@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.
Files changed (38) hide show
  1. package/README.md +25 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +29 -68
  4. package/dist/index.d.ts +28 -67
  5. package/dist/index.js +1 -1
  6. package/package.json +17 -16
  7. package/src/hook/helper/useReferralInteraction.test.ts +309 -0
  8. package/src/hook/helper/useReferralInteraction.ts +73 -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,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
+ }