@frak-labs/react-sdk 0.1.1 → 0.2.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.
Files changed (38) hide show
  1. package/README.md +25 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +28 -64
  4. package/dist/index.d.ts +28 -64
  5. package/dist/index.js +1 -1
  6. package/package.json +17 -16
  7. package/src/hook/helper/useReferralInteraction.test.ts +358 -0
  8. package/src/hook/helper/useReferralInteraction.ts +78 -0
  9. package/src/hook/index.ts +10 -0
  10. package/src/hook/useDisplayModal.test.ts +275 -0
  11. package/src/hook/useDisplayModal.ts +68 -0
  12. package/src/hook/useFrakClient.test.ts +119 -0
  13. package/src/hook/useFrakClient.ts +11 -0
  14. package/src/hook/useFrakConfig.test.ts +184 -0
  15. package/src/hook/useFrakConfig.ts +22 -0
  16. package/src/hook/useGetMerchantInformation.ts +56 -0
  17. package/src/hook/useOpenSso.test.ts +202 -0
  18. package/src/hook/useOpenSso.ts +51 -0
  19. package/src/hook/usePrepareSso.test.ts +197 -0
  20. package/src/hook/usePrepareSso.ts +55 -0
  21. package/src/hook/useSendTransaction.test.ts +218 -0
  22. package/src/hook/useSendTransaction.ts +62 -0
  23. package/src/hook/useSiweAuthenticate.test.ts +258 -0
  24. package/src/hook/useSiweAuthenticate.ts +66 -0
  25. package/src/hook/useWalletStatus.test.ts +112 -0
  26. package/src/hook/useWalletStatus.ts +55 -0
  27. package/src/hook/utils/useFrakContext.test.ts +157 -0
  28. package/src/hook/utils/useFrakContext.ts +42 -0
  29. package/src/hook/utils/useMounted.test.ts +70 -0
  30. package/src/hook/utils/useMounted.ts +12 -0
  31. package/src/hook/utils/useWindowLocation.test.ts +54 -0
  32. package/src/hook/utils/useWindowLocation.ts +40 -0
  33. package/src/index.ts +25 -0
  34. package/src/provider/FrakConfigProvider.test.ts +246 -0
  35. package/src/provider/FrakConfigProvider.ts +54 -0
  36. package/src/provider/FrakIFrameClientProvider.test.tsx +209 -0
  37. package/src/provider/FrakIFrameClientProvider.ts +86 -0
  38. package/src/provider/index.ts +7 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Tests for useDisplayModal hook
3
+ * Tests TanStack Mutation wrapper for displaying modals
4
+ */
5
+
6
+ import { vi } from "vitest";
7
+
8
+ vi.mock("@frak-labs/core-sdk/actions");
9
+
10
+ import type {
11
+ LoginModalStepType,
12
+ ModalRpcStepsResultType,
13
+ SendTransactionModalStepType,
14
+ } from "@frak-labs/core-sdk";
15
+ import { displayModal } from "@frak-labs/core-sdk/actions";
16
+ import { ClientNotFound } from "@frak-labs/frame-connector";
17
+ import { renderHook, waitFor } from "@testing-library/react";
18
+ import { describe, expect, test } from "../../tests/vitest-fixtures";
19
+ import { useDisplayModal } from "./useDisplayModal";
20
+
21
+ describe("useDisplayModal", () => {
22
+ test("should throw ClientNotFound when client is not available", async ({
23
+ queryWrapper,
24
+ }) => {
25
+ const { result } = renderHook(() => useDisplayModal(), {
26
+ wrapper: queryWrapper.wrapper,
27
+ });
28
+
29
+ await waitFor(() => {
30
+ expect(result.current.mutate).toBeDefined();
31
+ });
32
+
33
+ result.current.mutate({
34
+ steps: {
35
+ login: {},
36
+ },
37
+ });
38
+
39
+ await waitFor(() => {
40
+ expect(result.current.isError).toBe(true);
41
+ });
42
+
43
+ expect(result.current.error).toBeInstanceOf(ClientNotFound);
44
+ });
45
+
46
+ test("should display modal with login step", async ({
47
+ mockFrakProviders,
48
+ }) => {
49
+ const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
50
+ login: {
51
+ wallet: "0x1234567890123456789012345678901234567890",
52
+ },
53
+ };
54
+
55
+ vi.mocked(displayModal).mockResolvedValue(mockResult as any);
56
+
57
+ const { result } = renderHook(
58
+ () => useDisplayModal<[LoginModalStepType]>(),
59
+ {
60
+ wrapper: mockFrakProviders,
61
+ }
62
+ );
63
+
64
+ result.current.mutate({
65
+ steps: {
66
+ login: {},
67
+ },
68
+ });
69
+
70
+ await waitFor(() => {
71
+ expect(result.current.isSuccess).toBe(true);
72
+ });
73
+
74
+ expect(result.current.data).toEqual(mockResult);
75
+ expect(displayModal).toHaveBeenCalledTimes(1);
76
+ });
77
+
78
+ test("should display modal with sendTransaction step", async ({
79
+ mockFrakProviders,
80
+ }) => {
81
+ const mockResult: ModalRpcStepsResultType<
82
+ [SendTransactionModalStepType]
83
+ > = {
84
+ sendTransaction: {
85
+ hash: "0x1234567890123456789012345678901234567890123456789012345678901234",
86
+ },
87
+ };
88
+
89
+ vi.mocked(displayModal).mockResolvedValue(mockResult as any);
90
+
91
+ const { result } = renderHook(
92
+ () => useDisplayModal<[SendTransactionModalStepType]>(),
93
+ {
94
+ wrapper: mockFrakProviders,
95
+ }
96
+ );
97
+
98
+ result.current.mutate({
99
+ steps: {
100
+ sendTransaction: {
101
+ tx: {
102
+ to: "0x1234567890123456789012345678901234567890",
103
+ data: "0x",
104
+ },
105
+ },
106
+ },
107
+ });
108
+
109
+ await waitFor(() => {
110
+ expect(result.current.isSuccess).toBe(true);
111
+ });
112
+
113
+ expect(result.current.data).toEqual(mockResult);
114
+ });
115
+
116
+ test("should display modal with custom metadata", async ({
117
+ mockFrakProviders,
118
+ }) => {
119
+ const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
120
+ login: { wallet: "0x1234567890123456789012345678901234567890" },
121
+ };
122
+
123
+ vi.mocked(displayModal).mockResolvedValue(mockResult as any);
124
+
125
+ const { result } = renderHook(
126
+ () => useDisplayModal<[LoginModalStepType]>(),
127
+ {
128
+ wrapper: mockFrakProviders,
129
+ }
130
+ );
131
+
132
+ result.current.mutate({
133
+ steps: {
134
+ login: {},
135
+ },
136
+ metadata: {
137
+ header: {
138
+ title: "Custom Login",
139
+ },
140
+ },
141
+ });
142
+
143
+ await waitFor(() => {
144
+ expect(result.current.isSuccess).toBe(true);
145
+ });
146
+
147
+ expect(displayModal).toHaveBeenCalledWith(
148
+ expect.anything(),
149
+ expect.objectContaining({
150
+ metadata: expect.objectContaining({
151
+ header: {
152
+ title: "Custom Login",
153
+ },
154
+ }),
155
+ })
156
+ );
157
+ });
158
+
159
+ test("should handle mutateAsync", async ({ mockFrakProviders }) => {
160
+ const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
161
+ login: { wallet: "0x1234567890123456789012345678901234567890" },
162
+ };
163
+
164
+ vi.mocked(displayModal).mockResolvedValue(mockResult as any);
165
+
166
+ const { result } = renderHook(
167
+ () => useDisplayModal<[LoginModalStepType]>(),
168
+ {
169
+ wrapper: mockFrakProviders,
170
+ }
171
+ );
172
+
173
+ const response = await result.current.mutateAsync({
174
+ steps: {
175
+ login: {},
176
+ },
177
+ });
178
+
179
+ expect(response).toEqual(mockResult);
180
+
181
+ await waitFor(() => {
182
+ expect(result.current.isSuccess).toBe(true);
183
+ });
184
+ });
185
+
186
+ test("should handle RPC errors", async ({ mockFrakProviders }) => {
187
+ const error = new Error("Modal display failed");
188
+ vi.mocked(displayModal).mockRejectedValue(error);
189
+
190
+ const { result } = renderHook(() => useDisplayModal(), {
191
+ wrapper: mockFrakProviders,
192
+ });
193
+
194
+ result.current.mutate({
195
+ steps: {
196
+ login: {},
197
+ },
198
+ });
199
+
200
+ await waitFor(() => {
201
+ expect(result.current.isError).toBe(true);
202
+ });
203
+
204
+ expect(result.current.error).toEqual(error);
205
+ });
206
+
207
+ test("should handle mutation options", async ({ mockFrakProviders }) => {
208
+ const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
209
+ login: { wallet: "0x1234567890123456789012345678901234567890" },
210
+ };
211
+
212
+ vi.mocked(displayModal).mockResolvedValue(mockResult as any);
213
+
214
+ const onSuccess = vi.fn();
215
+ const onError = vi.fn();
216
+
217
+ const { result } = renderHook(
218
+ () =>
219
+ useDisplayModal<[LoginModalStepType]>({
220
+ mutations: {
221
+ onSuccess,
222
+ onError,
223
+ },
224
+ }),
225
+ {
226
+ wrapper: mockFrakProviders,
227
+ }
228
+ );
229
+
230
+ result.current.mutate({
231
+ steps: {
232
+ login: {},
233
+ },
234
+ });
235
+
236
+ await waitFor(() => {
237
+ expect(result.current.isSuccess).toBe(true);
238
+ });
239
+
240
+ expect(onSuccess).toHaveBeenCalled();
241
+ expect(onError).not.toHaveBeenCalled();
242
+ });
243
+
244
+ test("should reset mutation state", async ({ mockFrakProviders }) => {
245
+ const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
246
+ login: { wallet: "0x1234567890123456789012345678901234567890" },
247
+ };
248
+
249
+ vi.mocked(displayModal).mockResolvedValue(mockResult as any);
250
+
251
+ const { result } = renderHook(
252
+ () => useDisplayModal<[LoginModalStepType]>(),
253
+ {
254
+ wrapper: mockFrakProviders,
255
+ }
256
+ );
257
+
258
+ result.current.mutate({
259
+ steps: {
260
+ login: {},
261
+ },
262
+ });
263
+
264
+ await waitFor(() => {
265
+ expect(result.current.isSuccess).toBe(true);
266
+ });
267
+
268
+ result.current.reset();
269
+
270
+ await waitFor(() => {
271
+ expect(result.current.data).toBeUndefined();
272
+ expect(result.current.isIdle).toBe(true);
273
+ });
274
+ });
275
+ });
@@ -0,0 +1,68 @@
1
+ import type {
2
+ DisplayModalParamsType,
3
+ ModalRpcStepsResultType,
4
+ ModalStepTypes,
5
+ } from "@frak-labs/core-sdk";
6
+ import { displayModal } from "@frak-labs/core-sdk/actions";
7
+ import { ClientNotFound, type FrakRpcError } from "@frak-labs/frame-connector";
8
+ import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
9
+ import { useFrakClient } from "./useFrakClient";
10
+
11
+ /** @ignore */
12
+ type MutationOptions<T extends ModalStepTypes[]> = Omit<
13
+ UseMutationOptions<
14
+ ModalRpcStepsResultType<T>,
15
+ FrakRpcError,
16
+ DisplayModalParamsType<T>
17
+ >,
18
+ "mutationFn" | "mutationKey"
19
+ >;
20
+
21
+ /** @inline */
22
+ interface UseDisplayModalParams<T extends ModalStepTypes[] = ModalStepTypes[]> {
23
+ /**
24
+ * Optional mutation options, see {@link @tanstack/react-query!useMutation | `useMutation()`} for more infos
25
+ */
26
+ mutations?: MutationOptions<T>;
27
+ }
28
+
29
+ /**
30
+ * Hook that return a mutation helping to display a modal to the user
31
+ *
32
+ * It's a {@link @tanstack/react-query!home | `tanstack`} wrapper around the {@link @frak-labs/core-sdk!actions.displayModal | `displayModal()`} action
33
+ *
34
+ * @param args - Optional config object with `mutations` for customizing the underlying {@link @tanstack/react-query!useMutation | `useMutation()`}
35
+ *
36
+ * @typeParam T
37
+ * The modal steps types to display (the result will correspond to the steps types asked in params)
38
+ * An array of {@link @frak-labs/core-sdk!index.ModalStepTypes | `ModalStepTypes`}
39
+ * If not provided, it will default to a generic array of `ModalStepTypes`
40
+ *
41
+ * @group hooks
42
+ *
43
+ * @returns
44
+ * The mutation hook wrapping the `displayModal()` action
45
+ * The `mutate` and `mutateAsync` argument is of type {@link @frak-labs/core-sdk!index.DisplayModalParamsType | `DisplayModalParamsType<T>`}, with type params `T` being the modal steps types to display
46
+ * The `data` result is a {@link @frak-labs/core-sdk!index.ModalRpcStepsResultType | `ModalRpcStepsResultType`}
47
+ *
48
+ * @see {@link @frak-labs/core-sdk!actions.displayModal | `displayModal()`} for more info about the underlying action
49
+ * @see {@link @tanstack/react-query!useMutation | `useMutation()`} for more info about the mutation options and response
50
+ */
51
+ export function useDisplayModal<T extends ModalStepTypes[] = ModalStepTypes[]>({
52
+ mutations,
53
+ }: UseDisplayModalParams<T> = {}) {
54
+ const client = useFrakClient();
55
+
56
+ return useMutation({
57
+ ...mutations,
58
+ mutationKey: ["frak-sdk", "display-modal"],
59
+ mutationFn: async (args: DisplayModalParamsType<T>) => {
60
+ if (!client) {
61
+ throw new ClientNotFound();
62
+ }
63
+
64
+ // Ask to display the modal
65
+ return displayModal(client, args);
66
+ },
67
+ });
68
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Tests for useFrakClient hook
3
+ * Tests that the hook correctly retrieves the FrakClient from context
4
+ */
5
+
6
+ import { renderHook } from "@testing-library/react";
7
+ import { createElement } from "react";
8
+ import { describe, expect, test } from "../../tests/vitest-fixtures";
9
+ import { FrakIFrameClientContext } from "../provider/FrakIFrameClientProvider";
10
+ import { useFrakClient } from "./useFrakClient";
11
+
12
+ describe("useFrakClient", () => {
13
+ test("should return undefined when used outside provider", () => {
14
+ const { result } = renderHook(() => useFrakClient());
15
+
16
+ expect(result.current).toBeUndefined();
17
+ });
18
+
19
+ test("should return client when used inside provider", ({
20
+ mockFrakClient,
21
+ }) => {
22
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
23
+ createElement(
24
+ FrakIFrameClientContext.Provider,
25
+ { value: mockFrakClient },
26
+ children
27
+ );
28
+
29
+ const { result } = renderHook(() => useFrakClient(), {
30
+ wrapper,
31
+ });
32
+
33
+ expect(result.current).toBe(mockFrakClient);
34
+ });
35
+
36
+ test("should return the same client instance across re-renders", ({
37
+ mockFrakClient,
38
+ }) => {
39
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
40
+ createElement(
41
+ FrakIFrameClientContext.Provider,
42
+ { value: mockFrakClient },
43
+ children
44
+ );
45
+
46
+ const { result, rerender } = renderHook(() => useFrakClient(), {
47
+ wrapper,
48
+ });
49
+
50
+ const firstClient = result.current;
51
+ rerender();
52
+ const secondClient = result.current;
53
+
54
+ expect(firstClient).toBe(secondClient);
55
+ expect(firstClient).toBe(mockFrakClient);
56
+ });
57
+
58
+ test("should update when provider value changes", ({
59
+ mockFrakClient,
60
+ mockFrakConfig,
61
+ }) => {
62
+ const newMockClient = {
63
+ ...mockFrakClient,
64
+ config: {
65
+ ...mockFrakConfig,
66
+ domain: "new-domain.com",
67
+ },
68
+ };
69
+
70
+ let currentClient = mockFrakClient;
71
+
72
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
73
+ createElement(
74
+ FrakIFrameClientContext.Provider,
75
+ { value: currentClient },
76
+ children
77
+ );
78
+
79
+ const { result, rerender } = renderHook(() => useFrakClient(), {
80
+ wrapper,
81
+ });
82
+
83
+ // Initially returns first client
84
+ expect(result.current).toBe(mockFrakClient);
85
+
86
+ // Update the client in provider
87
+ currentClient = newMockClient;
88
+ rerender();
89
+
90
+ // Should return new client
91
+ expect(result.current).toBe(newMockClient);
92
+ expect(result.current?.config.domain).toBe("new-domain.com");
93
+ });
94
+
95
+ test("should handle client becoming undefined", ({ mockFrakClient }) => {
96
+ let currentClient: typeof mockFrakClient | undefined = mockFrakClient;
97
+
98
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
99
+ createElement(
100
+ FrakIFrameClientContext.Provider,
101
+ { value: currentClient },
102
+ children
103
+ );
104
+
105
+ const { result, rerender } = renderHook(() => useFrakClient(), {
106
+ wrapper,
107
+ });
108
+
109
+ // Initially returns client
110
+ expect(result.current).toBe(mockFrakClient);
111
+
112
+ // Set client to undefined
113
+ currentClient = undefined;
114
+ rerender();
115
+
116
+ // Should return undefined
117
+ expect(result.current).toBeUndefined();
118
+ });
119
+ });
@@ -0,0 +1,11 @@
1
+ import { useContext } from "react";
2
+ import { FrakIFrameClientContext } from "../provider";
3
+
4
+ /**
5
+ * Get the current Frak client
6
+ *
7
+ * @group hooks
8
+ */
9
+ export function useFrakClient() {
10
+ return useContext(FrakIFrameClientContext);
11
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Tests for useFrakConfig hook
3
+ * Tests that the hook correctly retrieves the FrakConfig from context
4
+ * and throws error when used outside provider
5
+ */
6
+
7
+ import { FrakRpcError } from "@frak-labs/frame-connector";
8
+ import { renderHook } from "@testing-library/react";
9
+ import React from "react";
10
+ import { describe, expect, test, vi } from "../../tests/vitest-fixtures";
11
+ import { FrakConfigProvider } from "../provider/FrakConfigProvider";
12
+ import { useFrakConfig } from "./useFrakConfig";
13
+
14
+ describe("useFrakConfig", () => {
15
+ test("should throw FrakRpcError when used outside provider", () => {
16
+ // Mock console.error to avoid noise in test output
17
+ const consoleErrorSpy = vi
18
+ .spyOn(console, "error")
19
+ .mockImplementation(() => {});
20
+
21
+ expect(() => {
22
+ renderHook(() => useFrakConfig());
23
+ }).toThrow(FrakRpcError);
24
+
25
+ try {
26
+ renderHook(() => useFrakConfig());
27
+ } catch (error) {
28
+ expect(error).toBeInstanceOf(FrakRpcError);
29
+ // FrakRpcError message contains the error code
30
+ expect((error as FrakRpcError).message).toContain("-32002");
31
+ }
32
+
33
+ consoleErrorSpy.mockRestore();
34
+ });
35
+
36
+ test("should return config when used inside provider", ({
37
+ mockFrakConfig,
38
+ }) => {
39
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
40
+ React.createElement(
41
+ FrakConfigProvider,
42
+ { config: mockFrakConfig },
43
+ children
44
+ );
45
+
46
+ const { result } = renderHook(() => useFrakConfig(), {
47
+ wrapper,
48
+ });
49
+
50
+ expect(result.current).toBeDefined();
51
+ expect(result.current.domain).toBe(mockFrakConfig.domain);
52
+ expect(result.current.metadata?.name).toBe(
53
+ mockFrakConfig.metadata?.name
54
+ );
55
+ });
56
+
57
+ test("should apply default walletUrl if not provided", () => {
58
+ const configWithoutWalletUrl = {
59
+ domain: "example.com",
60
+ metadata: {
61
+ name: "Test App",
62
+ },
63
+ };
64
+
65
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
66
+ React.createElement(
67
+ FrakConfigProvider,
68
+ { config: configWithoutWalletUrl },
69
+ children
70
+ );
71
+
72
+ const { result } = renderHook(() => useFrakConfig(), {
73
+ wrapper,
74
+ });
75
+
76
+ expect(result.current.walletUrl).toBe("https://wallet.frak.id");
77
+ });
78
+
79
+ test("should use provided walletUrl if specified", ({ mockFrakConfig }) => {
80
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
81
+ React.createElement(
82
+ FrakConfigProvider,
83
+ { config: mockFrakConfig },
84
+ children
85
+ );
86
+
87
+ const { result } = renderHook(() => useFrakConfig(), {
88
+ wrapper,
89
+ });
90
+
91
+ expect(result.current.walletUrl).toBe(mockFrakConfig.walletUrl);
92
+ });
93
+
94
+ test("should return stable config across re-renders", ({
95
+ mockFrakConfig,
96
+ }) => {
97
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
98
+ React.createElement(
99
+ FrakConfigProvider,
100
+ { config: mockFrakConfig },
101
+ children
102
+ );
103
+
104
+ const { result, rerender } = renderHook(() => useFrakConfig(), {
105
+ wrapper,
106
+ });
107
+
108
+ const firstConfig = result.current;
109
+ rerender();
110
+ const secondConfig = result.current;
111
+
112
+ // Config values should remain stable
113
+ expect(firstConfig.domain).toBe(secondConfig.domain);
114
+ expect(firstConfig.walletUrl).toBe(secondConfig.walletUrl);
115
+ expect(firstConfig.domain).toBe(mockFrakConfig.domain);
116
+ });
117
+
118
+ test("should include all config properties", ({ mockFrakConfig }) => {
119
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
120
+ React.createElement(
121
+ FrakConfigProvider,
122
+ { config: mockFrakConfig },
123
+ children
124
+ );
125
+
126
+ const { result } = renderHook(() => useFrakConfig(), {
127
+ wrapper,
128
+ });
129
+
130
+ expect(result.current.domain).toBe(mockFrakConfig.domain);
131
+ expect(result.current.walletUrl).toBe(mockFrakConfig.walletUrl);
132
+ expect(result.current.metadata).toEqual(mockFrakConfig.metadata);
133
+ expect(result.current.customizations).toEqual(
134
+ mockFrakConfig.customizations
135
+ );
136
+ });
137
+
138
+ test("should handle minimal config", () => {
139
+ const minimalConfig = {
140
+ domain: "minimal.com",
141
+ metadata: {
142
+ name: "Minimal Test App",
143
+ },
144
+ };
145
+
146
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
147
+ React.createElement(
148
+ FrakConfigProvider,
149
+ { config: minimalConfig },
150
+ children
151
+ );
152
+
153
+ const { result } = renderHook(() => useFrakConfig(), {
154
+ wrapper,
155
+ });
156
+
157
+ expect(result.current.domain).toBe("minimal.com");
158
+ expect(result.current.walletUrl).toBe("https://wallet.frak.id");
159
+ expect(result.current.metadata.name).toBe("Minimal Test App");
160
+ expect(result.current.customizations).toBeUndefined();
161
+ });
162
+
163
+ test("should fallback to window.location.host if domain not provided", () => {
164
+ const configWithoutDomain = {
165
+ metadata: {
166
+ name: "Test App",
167
+ },
168
+ };
169
+
170
+ const wrapper = ({ children }: { children: React.ReactNode }) =>
171
+ React.createElement(
172
+ FrakConfigProvider,
173
+ { config: configWithoutDomain as any },
174
+ children
175
+ );
176
+
177
+ const { result } = renderHook(() => useFrakConfig(), {
178
+ wrapper,
179
+ });
180
+
181
+ // In test environment, window.location.host is "localhost:3000" (JSDOM default)
182
+ expect(result.current.domain).toBe(window.location.host);
183
+ });
184
+ });
@@ -0,0 +1,22 @@
1
+ import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
2
+ import { useContext } from "react";
3
+ import { FrakConfigContext } from "../provider";
4
+
5
+ /**
6
+ * Get the current Frak config
7
+ * @throws {FrakRpcError} if the config is not found (only if this hooks is used outside a FrakConfigProvider)
8
+ * @group hooks
9
+ *
10
+ * @see {@link @frak-labs/react-sdk!FrakConfigProvider | FrakConfigProvider} for the config provider
11
+ * @see {@link @frak-labs/core-sdk!index.FrakWalletSdkConfig | FrakWalletSdkConfig} for the config type
12
+ */
13
+ export function useFrakConfig() {
14
+ const config = useContext(FrakConfigContext);
15
+ if (!config) {
16
+ throw new FrakRpcError(
17
+ RpcErrorCodes.configError,
18
+ "Frak config not found"
19
+ );
20
+ }
21
+ return config;
22
+ }