@frak-labs/react-sdk 0.1.1 → 0.2.0-beta.7898df5b
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/README.md +25 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +29 -68
- package/dist/index.d.ts +28 -67
- package/dist/index.js +1 -1
- package/package.json +17 -16
- package/src/hook/helper/useReferralInteraction.test.ts +309 -0
- package/src/hook/helper/useReferralInteraction.ts +73 -0
- package/src/hook/index.ts +10 -0
- package/src/hook/useDisplayModal.test.ts +275 -0
- package/src/hook/useDisplayModal.ts +68 -0
- package/src/hook/useFrakClient.test.ts +119 -0
- package/src/hook/useFrakClient.ts +11 -0
- package/src/hook/useFrakConfig.test.ts +184 -0
- package/src/hook/useFrakConfig.ts +22 -0
- package/src/hook/useGetMerchantInformation.ts +56 -0
- package/src/hook/useOpenSso.test.ts +202 -0
- package/src/hook/useOpenSso.ts +51 -0
- package/src/hook/usePrepareSso.test.ts +197 -0
- package/src/hook/usePrepareSso.ts +55 -0
- package/src/hook/useSendTransaction.test.ts +218 -0
- package/src/hook/useSendTransaction.ts +62 -0
- package/src/hook/useSiweAuthenticate.test.ts +258 -0
- package/src/hook/useSiweAuthenticate.ts +66 -0
- package/src/hook/useWalletStatus.test.ts +112 -0
- package/src/hook/useWalletStatus.ts +55 -0
- package/src/hook/utils/useFrakContext.test.ts +157 -0
- package/src/hook/utils/useFrakContext.ts +42 -0
- package/src/hook/utils/useMounted.test.ts +70 -0
- package/src/hook/utils/useMounted.ts +12 -0
- package/src/hook/utils/useWindowLocation.test.ts +54 -0
- package/src/hook/utils/useWindowLocation.ts +40 -0
- package/src/index.ts +25 -0
- package/src/provider/FrakConfigProvider.test.ts +246 -0
- package/src/provider/FrakConfigProvider.ts +54 -0
- package/src/provider/FrakIFrameClientProvider.test.tsx +209 -0
- package/src/provider/FrakIFrameClientProvider.ts +86 -0
- package/src/provider/index.ts +7 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for useSiweAuthenticate hook
|
|
3
|
+
* Tests TanStack Mutation wrapper for SIWE authentication
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
vi.mock("@frak-labs/core-sdk/actions");
|
|
9
|
+
|
|
10
|
+
import type { SiweAuthenticateReturnType } from "@frak-labs/core-sdk";
|
|
11
|
+
import { siweAuthenticate } from "@frak-labs/core-sdk/actions";
|
|
12
|
+
import { ClientNotFound } from "@frak-labs/frame-connector";
|
|
13
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
14
|
+
import { describe, expect, test } from "../../tests/vitest-fixtures";
|
|
15
|
+
import { useSiweAuthenticate } from "./useSiweAuthenticate";
|
|
16
|
+
|
|
17
|
+
describe("useSiweAuthenticate", () => {
|
|
18
|
+
test("should throw ClientNotFound when client is not available", async ({
|
|
19
|
+
queryWrapper,
|
|
20
|
+
}) => {
|
|
21
|
+
const { result } = renderHook(() => useSiweAuthenticate(), {
|
|
22
|
+
wrapper: queryWrapper.wrapper,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await waitFor(() => {
|
|
26
|
+
expect(result.current.mutate).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
result.current.mutate({
|
|
30
|
+
siwe: {
|
|
31
|
+
domain: "example.com",
|
|
32
|
+
uri: "https://example.com",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await waitFor(() => {
|
|
37
|
+
expect(result.current.isError).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(result.current.error).toBeInstanceOf(ClientNotFound);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("should authenticate with SIWE successfully", async ({
|
|
44
|
+
mockFrakProviders,
|
|
45
|
+
}) => {
|
|
46
|
+
const mockResult: SiweAuthenticateReturnType = {
|
|
47
|
+
signature: "0xsignature123",
|
|
48
|
+
message: "Example message to sign",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
vi.mocked(siweAuthenticate).mockResolvedValue(mockResult);
|
|
52
|
+
|
|
53
|
+
const { result } = renderHook(() => useSiweAuthenticate(), {
|
|
54
|
+
wrapper: mockFrakProviders,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
result.current.mutate({
|
|
58
|
+
siwe: {
|
|
59
|
+
domain: "example.com",
|
|
60
|
+
uri: "https://example.com",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await waitFor(() => {
|
|
65
|
+
expect(result.current.isSuccess).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(result.current.data).toEqual(mockResult);
|
|
69
|
+
expect(siweAuthenticate).toHaveBeenCalledTimes(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should authenticate with custom SIWE params", async ({
|
|
73
|
+
mockFrakProviders,
|
|
74
|
+
}) => {
|
|
75
|
+
const mockResult: SiweAuthenticateReturnType = {
|
|
76
|
+
signature: "0xsignature456",
|
|
77
|
+
message: "Custom message",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
vi.mocked(siweAuthenticate).mockResolvedValue(mockResult);
|
|
81
|
+
|
|
82
|
+
const { result } = renderHook(() => useSiweAuthenticate(), {
|
|
83
|
+
wrapper: mockFrakProviders,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const siweParams = {
|
|
87
|
+
domain: "custom.com",
|
|
88
|
+
uri: "https://custom.com/auth",
|
|
89
|
+
statement: "Sign in to Custom App",
|
|
90
|
+
nonce: "random-nonce-123",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
result.current.mutate({
|
|
94
|
+
siwe: siweParams,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await waitFor(() => {
|
|
98
|
+
expect(result.current.isSuccess).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(result.current.data).toEqual(mockResult);
|
|
102
|
+
expect(siweAuthenticate).toHaveBeenCalledWith(
|
|
103
|
+
expect.anything(),
|
|
104
|
+
expect.objectContaining({
|
|
105
|
+
siwe: siweParams,
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("should authenticate with metadata", async ({ mockFrakProviders }) => {
|
|
111
|
+
const mockResult: SiweAuthenticateReturnType = {
|
|
112
|
+
signature: "0xsignature789",
|
|
113
|
+
message: "Message with metadata",
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
vi.mocked(siweAuthenticate).mockResolvedValue(mockResult);
|
|
117
|
+
|
|
118
|
+
const { result } = renderHook(() => useSiweAuthenticate(), {
|
|
119
|
+
wrapper: mockFrakProviders,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
result.current.mutate({
|
|
123
|
+
siwe: {
|
|
124
|
+
domain: "example.com",
|
|
125
|
+
uri: "https://example.com",
|
|
126
|
+
},
|
|
127
|
+
metadata: {
|
|
128
|
+
header: {
|
|
129
|
+
title: "Sign In with Ethereum",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await waitFor(() => {
|
|
135
|
+
expect(result.current.isSuccess).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(result.current.data).toEqual(mockResult);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("should handle mutateAsync", async ({ mockFrakProviders }) => {
|
|
142
|
+
const mockResult: SiweAuthenticateReturnType = {
|
|
143
|
+
signature: "0xasync123",
|
|
144
|
+
message: "Async message",
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
vi.mocked(siweAuthenticate).mockResolvedValue(mockResult);
|
|
148
|
+
|
|
149
|
+
const { result } = renderHook(() => useSiweAuthenticate(), {
|
|
150
|
+
wrapper: mockFrakProviders,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const response = await result.current.mutateAsync({
|
|
154
|
+
siwe: {
|
|
155
|
+
domain: "example.com",
|
|
156
|
+
uri: "https://example.com",
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(response).toEqual(mockResult);
|
|
161
|
+
|
|
162
|
+
await waitFor(() => {
|
|
163
|
+
expect(result.current.isSuccess).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("should handle RPC errors", async ({ mockFrakProviders }) => {
|
|
168
|
+
const error = new Error("SIWE authentication failed");
|
|
169
|
+
vi.mocked(siweAuthenticate).mockRejectedValue(error);
|
|
170
|
+
|
|
171
|
+
const { result } = renderHook(() => useSiweAuthenticate(), {
|
|
172
|
+
wrapper: mockFrakProviders,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
result.current.mutate({
|
|
176
|
+
siwe: {
|
|
177
|
+
domain: "example.com",
|
|
178
|
+
uri: "https://example.com",
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await waitFor(() => {
|
|
183
|
+
expect(result.current.isError).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(result.current.error).toEqual(error);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("should handle mutation options", async ({ mockFrakProviders }) => {
|
|
190
|
+
const mockResult: SiweAuthenticateReturnType = {
|
|
191
|
+
signature: "0xoptions123",
|
|
192
|
+
message: "Options message",
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
vi.mocked(siweAuthenticate).mockResolvedValue(mockResult);
|
|
196
|
+
|
|
197
|
+
const onSuccess = vi.fn();
|
|
198
|
+
const onError = vi.fn();
|
|
199
|
+
|
|
200
|
+
const { result } = renderHook(
|
|
201
|
+
() =>
|
|
202
|
+
useSiweAuthenticate({
|
|
203
|
+
mutations: {
|
|
204
|
+
onSuccess,
|
|
205
|
+
onError,
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
{
|
|
209
|
+
wrapper: mockFrakProviders,
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
result.current.mutate({
|
|
214
|
+
siwe: {
|
|
215
|
+
domain: "example.com",
|
|
216
|
+
uri: "https://example.com",
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await waitFor(() => {
|
|
221
|
+
expect(result.current.isSuccess).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(onSuccess).toHaveBeenCalled();
|
|
225
|
+
expect(onError).not.toHaveBeenCalled();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("should reset mutation state", async ({ mockFrakProviders }) => {
|
|
229
|
+
const mockResult: SiweAuthenticateReturnType = {
|
|
230
|
+
signature: "0xreset123",
|
|
231
|
+
message: "Reset message",
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
vi.mocked(siweAuthenticate).mockResolvedValue(mockResult);
|
|
235
|
+
|
|
236
|
+
const { result } = renderHook(() => useSiweAuthenticate(), {
|
|
237
|
+
wrapper: mockFrakProviders,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
result.current.mutate({
|
|
241
|
+
siwe: {
|
|
242
|
+
domain: "example.com",
|
|
243
|
+
uri: "https://example.com",
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await waitFor(() => {
|
|
248
|
+
expect(result.current.isSuccess).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
result.current.reset();
|
|
252
|
+
|
|
253
|
+
await waitFor(() => {
|
|
254
|
+
expect(result.current.data).toBeUndefined();
|
|
255
|
+
expect(result.current.isIdle).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { SiweAuthenticateReturnType } from "@frak-labs/core-sdk";
|
|
2
|
+
import {
|
|
3
|
+
type SiweAuthenticateModalParams,
|
|
4
|
+
siweAuthenticate,
|
|
5
|
+
} from "@frak-labs/core-sdk/actions";
|
|
6
|
+
import { ClientNotFound, type FrakRpcError } from "@frak-labs/frame-connector";
|
|
7
|
+
import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
|
|
8
|
+
import { useFrakClient } from "./useFrakClient";
|
|
9
|
+
|
|
10
|
+
/** @inline */
|
|
11
|
+
type MutationOptions = Omit<
|
|
12
|
+
UseMutationOptions<
|
|
13
|
+
SiweAuthenticateReturnType,
|
|
14
|
+
FrakRpcError,
|
|
15
|
+
SiweAuthenticateModalParams
|
|
16
|
+
>,
|
|
17
|
+
"mutationFn" | "mutationKey"
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
/** @ignore */
|
|
21
|
+
interface UseSiweAuthenticateParams {
|
|
22
|
+
/**
|
|
23
|
+
* Optional mutation options, see {@link @tanstack/react-query!useMutation | `useMutation()`} for more infos
|
|
24
|
+
*/
|
|
25
|
+
mutations?: MutationOptions;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Hook that return a mutation helping to send perform a SIWE authentication
|
|
30
|
+
*
|
|
31
|
+
* It's a {@link @tanstack/react-query!home | `tanstack`} wrapper around the {@link @frak-labs/core-sdk!actions.siweAuthenticate | `siweAuthenticate()`} action
|
|
32
|
+
*
|
|
33
|
+
* @param args - Optional config object with `mutations` for customizing the underlying {@link @tanstack/react-query!useMutation | `useMutation()`}
|
|
34
|
+
*
|
|
35
|
+
* @group hooks
|
|
36
|
+
*
|
|
37
|
+
* @returns
|
|
38
|
+
* The mutation hook wrapping the `siweAuthenticate()` action
|
|
39
|
+
* The `mutate` and `mutateAsync` argument is of type {@link @frak-labs/core-sdk!actions.SiweAuthenticateModalParams | `SiweAuthenticateModalParams`}
|
|
40
|
+
* The `data` result is a {@link @frak-labs/core-sdk!index.SiweAuthenticateReturnType | `SiweAuthenticateReturnType`}
|
|
41
|
+
*
|
|
42
|
+
* @see {@link @frak-labs/core-sdk!actions.siweAuthenticate | `siweAuthenticate()`} for more info about the underlying action
|
|
43
|
+
* @see {@link @tanstack/react-query!useMutation | `useMutation()`} for more info about the mutation options and response
|
|
44
|
+
*/
|
|
45
|
+
export function useSiweAuthenticate({
|
|
46
|
+
mutations,
|
|
47
|
+
}: UseSiweAuthenticateParams = {}) {
|
|
48
|
+
const client = useFrakClient();
|
|
49
|
+
|
|
50
|
+
return useMutation({
|
|
51
|
+
...mutations,
|
|
52
|
+
mutationKey: [
|
|
53
|
+
"frak-sdk",
|
|
54
|
+
"siwe-authenticate",
|
|
55
|
+
client?.config.domain ?? "no-domain",
|
|
56
|
+
],
|
|
57
|
+
mutationFn: async (params: SiweAuthenticateModalParams) => {
|
|
58
|
+
if (!client) {
|
|
59
|
+
throw new ClientNotFound();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Launch the authentication
|
|
63
|
+
return siweAuthenticate(client, params);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for useWalletStatus hook
|
|
3
|
+
* Tests TanStack Query wrapper for watching wallet status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
vi.mock("@frak-labs/core-sdk/actions");
|
|
9
|
+
|
|
10
|
+
import type { WalletStatusReturnType } from "@frak-labs/core-sdk";
|
|
11
|
+
import { watchWalletStatus } from "@frak-labs/core-sdk/actions";
|
|
12
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
13
|
+
import { describe, expect, test } from "../../tests/vitest-fixtures";
|
|
14
|
+
import { useWalletStatus } from "./useWalletStatus";
|
|
15
|
+
|
|
16
|
+
describe("useWalletStatus", () => {
|
|
17
|
+
test("should be disabled when client is not available", ({
|
|
18
|
+
queryWrapper,
|
|
19
|
+
}) => {
|
|
20
|
+
const { result } = renderHook(() => useWalletStatus(), {
|
|
21
|
+
wrapper: queryWrapper.wrapper,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Query should not run when client is not available
|
|
25
|
+
expect(result.current.isPending).toBe(true);
|
|
26
|
+
expect(result.current.isFetching).toBe(false);
|
|
27
|
+
expect(result.current.data).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("should watch wallet status successfully", async ({
|
|
31
|
+
mockFrakProviders,
|
|
32
|
+
}) => {
|
|
33
|
+
const mockStatus: WalletStatusReturnType = {
|
|
34
|
+
key: "connected",
|
|
35
|
+
wallet: "0x1234567890123456789012345678901234567890",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
vi.mocked(watchWalletStatus).mockResolvedValue(mockStatus);
|
|
39
|
+
|
|
40
|
+
const { result } = renderHook(() => useWalletStatus(), {
|
|
41
|
+
wrapper: mockFrakProviders,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await waitFor(() => {
|
|
45
|
+
expect(result.current.isSuccess).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(result.current.data).toEqual(mockStatus);
|
|
49
|
+
expect(watchWalletStatus).toHaveBeenCalledTimes(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("should return not connected status", async ({
|
|
53
|
+
mockFrakProviders,
|
|
54
|
+
}) => {
|
|
55
|
+
const mockStatus: WalletStatusReturnType = {
|
|
56
|
+
key: "not-connected",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
vi.mocked(watchWalletStatus).mockResolvedValue(mockStatus);
|
|
60
|
+
|
|
61
|
+
const { result } = renderHook(() => useWalletStatus(), {
|
|
62
|
+
wrapper: mockFrakProviders,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(result.current.isSuccess).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(result.current.data).toEqual(mockStatus);
|
|
70
|
+
expect(result.current.data?.key).toBe("not-connected");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("should handle RPC errors", async ({ mockFrakProviders }) => {
|
|
74
|
+
const error = new Error("Wallet status watch failed");
|
|
75
|
+
vi.mocked(watchWalletStatus).mockRejectedValue(error);
|
|
76
|
+
|
|
77
|
+
const { result } = renderHook(() => useWalletStatus(), {
|
|
78
|
+
wrapper: mockFrakProviders,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await waitFor(() => {
|
|
82
|
+
expect(result.current.isError).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(result.current.error).toEqual(error);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("should pass callback to watchWalletStatus", async ({
|
|
89
|
+
mockFrakProviders,
|
|
90
|
+
mockFrakClient,
|
|
91
|
+
}) => {
|
|
92
|
+
const mockStatus: WalletStatusReturnType = {
|
|
93
|
+
key: "connected",
|
|
94
|
+
wallet: "0x1234567890123456789012345678901234567890",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
vi.mocked(watchWalletStatus).mockResolvedValue(mockStatus);
|
|
98
|
+
|
|
99
|
+
const { result } = renderHook(() => useWalletStatus(), {
|
|
100
|
+
wrapper: mockFrakProviders,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
expect(result.current.isSuccess).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(watchWalletStatus).toHaveBeenCalledWith(
|
|
108
|
+
mockFrakClient,
|
|
109
|
+
expect.any(Function)
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { WalletStatusReturnType } from "@frak-labs/core-sdk";
|
|
2
|
+
import { watchWalletStatus } from "@frak-labs/core-sdk/actions";
|
|
3
|
+
import { ClientNotFound } from "@frak-labs/frame-connector";
|
|
4
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
import { useCallback } from "react";
|
|
6
|
+
import { useFrakClient } from "./useFrakClient";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook that return a query helping to get the current wallet status.
|
|
10
|
+
*
|
|
11
|
+
* It's a {@link @tanstack/react-query!home | `tanstack`} wrapper around the {@link @frak-labs/core-sdk!actions.watchWalletStatus | `watchWalletStatus()`} action
|
|
12
|
+
*
|
|
13
|
+
* @group hooks
|
|
14
|
+
*
|
|
15
|
+
* @returns
|
|
16
|
+
* The query hook wrapping the `watchWalletStatus()` action
|
|
17
|
+
* The `data` result is a {@link @frak-labs/core-sdk!index.WalletStatusReturnType | `WalletStatusReturnType`}
|
|
18
|
+
*
|
|
19
|
+
* @see {@link @frak-labs/core-sdk!actions.watchWalletStatus | `watchWalletStatus()`} for more info about the underlying action
|
|
20
|
+
* @see {@link @tanstack/react-query!useQuery | `useQuery()`} for more info about the useQuery response
|
|
21
|
+
*/
|
|
22
|
+
export function useWalletStatus() {
|
|
23
|
+
const queryClient = useQueryClient();
|
|
24
|
+
const client = useFrakClient();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Callback hook when we receive an updated wallet status
|
|
28
|
+
*/
|
|
29
|
+
const newStatusUpdated = useCallback(
|
|
30
|
+
(event: WalletStatusReturnType) => {
|
|
31
|
+
queryClient.setQueryData(
|
|
32
|
+
["frak-sdk", "wallet-status-listener"],
|
|
33
|
+
event
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
[queryClient]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Setup the query listener
|
|
41
|
+
*/
|
|
42
|
+
return useQuery<WalletStatusReturnType>({
|
|
43
|
+
gcTime: 0,
|
|
44
|
+
staleTime: 0,
|
|
45
|
+
queryKey: ["frak-sdk", "wallet-status-listener"],
|
|
46
|
+
queryFn: async () => {
|
|
47
|
+
if (!client) {
|
|
48
|
+
throw new ClientNotFound();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return watchWalletStatus(client, newStatusUpdated);
|
|
52
|
+
},
|
|
53
|
+
enabled: !!client,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for useFrakContext hook
|
|
3
|
+
* Tests hook that extracts and manages Frak context from URL
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
vi.mock("@frak-labs/core-sdk", async () => {
|
|
9
|
+
const actual = await vi.importActual<typeof import("@frak-labs/core-sdk")>(
|
|
10
|
+
"@frak-labs/core-sdk"
|
|
11
|
+
);
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
FrakContextManager: {
|
|
15
|
+
parse: vi.fn(),
|
|
16
|
+
replaceUrl: vi.fn(),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
vi.mock("./useWindowLocation");
|
|
22
|
+
|
|
23
|
+
import type { FrakContext } from "@frak-labs/core-sdk";
|
|
24
|
+
import { FrakContextManager } from "@frak-labs/core-sdk";
|
|
25
|
+
import { renderHook } from "@testing-library/react";
|
|
26
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
27
|
+
import { useFrakContext } from "./useFrakContext";
|
|
28
|
+
import { useWindowLocation } from "./useWindowLocation";
|
|
29
|
+
|
|
30
|
+
describe("useFrakContext", () => {
|
|
31
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
consoleLogSpy.mockClear();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("should return null when no location href", () => {
|
|
42
|
+
vi.mocked(useWindowLocation).mockReturnValue({
|
|
43
|
+
location: undefined,
|
|
44
|
+
href: undefined,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { result } = renderHook(() => useFrakContext());
|
|
48
|
+
|
|
49
|
+
expect(result.current.frakContext).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("should parse frak context from URL", () => {
|
|
53
|
+
const mockContext: FrakContext = {
|
|
54
|
+
r: "0x1234567890123456789012345678901234567890",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
vi.mocked(useWindowLocation).mockReturnValue({
|
|
58
|
+
location: { href: "https://example.com?frak=test" } as Location,
|
|
59
|
+
href: "https://example.com?frak=test",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue(mockContext);
|
|
63
|
+
|
|
64
|
+
const { result } = renderHook(() => useFrakContext());
|
|
65
|
+
|
|
66
|
+
expect(FrakContextManager.parse).toHaveBeenCalledWith({
|
|
67
|
+
url: "https://example.com?frak=test",
|
|
68
|
+
});
|
|
69
|
+
expect(result.current.frakContext).toEqual(mockContext);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should update context with new values", () => {
|
|
73
|
+
vi.mocked(useWindowLocation).mockReturnValue({
|
|
74
|
+
location: { href: "https://example.com" } as Location,
|
|
75
|
+
href: "https://example.com",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue(null);
|
|
79
|
+
|
|
80
|
+
const { result } = renderHook(() => useFrakContext());
|
|
81
|
+
|
|
82
|
+
const newContext: Partial<FrakContext> = {
|
|
83
|
+
r: "0x4567890123456789012345678901234567890123",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
result.current.updateContext(newContext);
|
|
87
|
+
|
|
88
|
+
expect(console.log).toHaveBeenCalledWith("Updating context", {
|
|
89
|
+
newContext,
|
|
90
|
+
});
|
|
91
|
+
expect(FrakContextManager.replaceUrl).toHaveBeenCalledWith({
|
|
92
|
+
url: "https://example.com",
|
|
93
|
+
context: newContext,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("should memoize frak context based on href", () => {
|
|
98
|
+
const mockContext: FrakContext = {
|
|
99
|
+
r: "0x7890123456789012345678901234567890123456",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
vi.mocked(useWindowLocation).mockReturnValue({
|
|
103
|
+
location: { href: "https://example.com?test=1" } as Location,
|
|
104
|
+
href: "https://example.com?test=1",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue(mockContext);
|
|
108
|
+
|
|
109
|
+
const { result, rerender } = renderHook(() => useFrakContext());
|
|
110
|
+
|
|
111
|
+
const firstContext = result.current.frakContext;
|
|
112
|
+
|
|
113
|
+
// Rerender without changing href
|
|
114
|
+
rerender();
|
|
115
|
+
|
|
116
|
+
expect(result.current.frakContext).toBe(firstContext);
|
|
117
|
+
expect(FrakContextManager.parse).toHaveBeenCalledTimes(1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("should reparse context when href changes", () => {
|
|
121
|
+
const mockContext1: FrakContext = {
|
|
122
|
+
r: "0x1111111111111111111111111111111111111111",
|
|
123
|
+
};
|
|
124
|
+
const mockContext2: FrakContext = {
|
|
125
|
+
r: "0x2222222222222222222222222222222222222222",
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const { rerender } = renderHook(() => useFrakContext());
|
|
129
|
+
|
|
130
|
+
vi.mocked(useWindowLocation).mockReturnValue({
|
|
131
|
+
location: { href: "https://example.com?v=1" } as Location,
|
|
132
|
+
href: "https://example.com?v=1",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue(mockContext1);
|
|
136
|
+
|
|
137
|
+
rerender();
|
|
138
|
+
|
|
139
|
+
expect(FrakContextManager.parse).toHaveBeenCalledWith({
|
|
140
|
+
url: "https://example.com?v=1",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Change href
|
|
144
|
+
vi.mocked(useWindowLocation).mockReturnValue({
|
|
145
|
+
location: { href: "https://example.com?v=2" } as Location,
|
|
146
|
+
href: "https://example.com?v=2",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue(mockContext2);
|
|
150
|
+
|
|
151
|
+
rerender();
|
|
152
|
+
|
|
153
|
+
expect(FrakContextManager.parse).toHaveBeenCalledWith({
|
|
154
|
+
url: "https://example.com?v=2",
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type FrakContext, FrakContextManager } from "@frak-labs/core-sdk";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { useWindowLocation } from "./useWindowLocation";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract the current frak context from the url
|
|
7
|
+
* @ignore
|
|
8
|
+
*/
|
|
9
|
+
export function useFrakContext() {
|
|
10
|
+
// Get the current window location
|
|
11
|
+
const { location } = useWindowLocation();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fetching and parsing the current frak context
|
|
15
|
+
*/
|
|
16
|
+
const frakContext = useMemo(() => {
|
|
17
|
+
// If no url extracted yet, early exit
|
|
18
|
+
if (!location?.href) return null;
|
|
19
|
+
|
|
20
|
+
// Parse the current context
|
|
21
|
+
return FrakContextManager.parse({ url: location.href });
|
|
22
|
+
}, [location?.href]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Update the current context
|
|
26
|
+
*/
|
|
27
|
+
const updateContext = useCallback(
|
|
28
|
+
(newContext: Partial<FrakContext>) => {
|
|
29
|
+
console.log("Updating context", { newContext });
|
|
30
|
+
FrakContextManager.replaceUrl({
|
|
31
|
+
url: location?.href,
|
|
32
|
+
context: newContext,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
[location?.href]
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
frakContext,
|
|
40
|
+
updateContext,
|
|
41
|
+
};
|
|
42
|
+
}
|