@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,56 @@
1
+ import type { GetMerchantInformationReturnType } from "@frak-labs/core-sdk";
2
+ import { getMerchantInformation } from "@frak-labs/core-sdk/actions";
3
+ import { ClientNotFound, type FrakRpcError } from "@frak-labs/frame-connector";
4
+ import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
5
+ import { useFrakClient } from "./useFrakClient";
6
+
7
+ /** @ignore */
8
+ type QueryOptions = Omit<
9
+ UseQueryOptions<
10
+ GetMerchantInformationReturnType,
11
+ FrakRpcError,
12
+ GetMerchantInformationReturnType
13
+ >,
14
+ "queryKey" | "queryFn"
15
+ >;
16
+
17
+ /** @inline */
18
+ interface UseGetMerchantInformationParams {
19
+ /**
20
+ * Optional query options, see {@link @tanstack/react-query!useQuery | `useQuery()`} for more infos
21
+ */
22
+ query?: QueryOptions;
23
+ }
24
+
25
+ /**
26
+ * Hook that return a query helping to get the current merchant information
27
+ *
28
+ * It's a {@link @tanstack/react-query!home | `tanstack`} wrapper around the {@link @frak-labs/core-sdk!actions.getMerchantInformation | `getMerchantInformation()`} action
29
+ *
30
+ * @param args - Optional config object with `query` for customizing the underlying {@link @tanstack/react-query!useQuery | `useQuery()`}
31
+ *
32
+ * @group hooks
33
+ *
34
+ * @returns
35
+ * The query hook wrapping the `getMerchantInformation()` action
36
+ * The `data` result is a {@link @frak-labs/core-sdk!index.GetMerchantInformationReturnType | `GetMerchantInformationReturnType`}
37
+ *
38
+ * @see {@link @frak-labs/core-sdk!actions.getMerchantInformation | `getMerchantInformation()`} for more info about the underlying action
39
+ * @see {@link @tanstack/react-query!useQuery | `useQuery()`} for more info about the useQuery options and response
40
+ */
41
+ export function useGetMerchantInformation({
42
+ query,
43
+ }: UseGetMerchantInformationParams = {}) {
44
+ const client = useFrakClient();
45
+
46
+ return useQuery({
47
+ ...query,
48
+ queryKey: ["frak-sdk", "get-merchant-information"],
49
+ queryFn: async () => {
50
+ if (!client) {
51
+ throw new ClientNotFound();
52
+ }
53
+ return getMerchantInformation(client);
54
+ },
55
+ });
56
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Tests for useOpenSso hook
3
+ * Tests TanStack Mutation wrapper for opening SSO
4
+ */
5
+
6
+ import { vi } from "vitest";
7
+
8
+ vi.mock("@frak-labs/core-sdk/actions");
9
+
10
+ import type { OpenSsoReturnType } from "@frak-labs/core-sdk";
11
+ import { openSso } 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 { useOpenSso } from "./useOpenSso";
16
+
17
+ describe("useOpenSso", () => {
18
+ test("should throw ClientNotFound when client is not available", async ({
19
+ queryWrapper,
20
+ }) => {
21
+ const { result } = renderHook(() => useOpenSso(), {
22
+ wrapper: queryWrapper.wrapper,
23
+ });
24
+
25
+ await waitFor(() => {
26
+ expect(result.current.mutate).toBeDefined();
27
+ });
28
+
29
+ result.current.mutate({});
30
+
31
+ await waitFor(() => {
32
+ expect(result.current.isError).toBe(true);
33
+ });
34
+
35
+ expect(result.current.error).toBeInstanceOf(ClientNotFound);
36
+ });
37
+
38
+ test("should open SSO successfully", async ({ mockFrakProviders }) => {
39
+ const mockResult = undefined as unknown as OpenSsoReturnType;
40
+
41
+ vi.mocked(openSso).mockResolvedValue(mockResult);
42
+
43
+ const { result } = renderHook(() => useOpenSso(), {
44
+ wrapper: mockFrakProviders,
45
+ });
46
+
47
+ result.current.mutate({});
48
+
49
+ await waitFor(() => {
50
+ expect(result.current.isSuccess).toBe(true);
51
+ });
52
+
53
+ expect(openSso).toHaveBeenCalledTimes(1);
54
+ });
55
+
56
+ test("should open SSO with redirectUrl", async ({ mockFrakProviders }) => {
57
+ const mockResult = undefined as unknown as OpenSsoReturnType;
58
+
59
+ vi.mocked(openSso).mockResolvedValue(mockResult);
60
+
61
+ const { result } = renderHook(() => useOpenSso(), {
62
+ wrapper: mockFrakProviders,
63
+ });
64
+
65
+ const redirectUrl = "https://example.com/callback";
66
+
67
+ result.current.mutate({
68
+ redirectUrl,
69
+ });
70
+
71
+ await waitFor(() => {
72
+ expect(result.current.isSuccess).toBe(true);
73
+ });
74
+
75
+ expect(openSso).toHaveBeenCalledWith(
76
+ expect.anything(),
77
+ expect.objectContaining({
78
+ redirectUrl,
79
+ })
80
+ );
81
+ });
82
+
83
+ test("should open SSO with metadata", async ({ mockFrakProviders }) => {
84
+ const mockResult = undefined as unknown as OpenSsoReturnType;
85
+
86
+ vi.mocked(openSso).mockResolvedValue(mockResult);
87
+
88
+ const { result } = renderHook(() => useOpenSso(), {
89
+ wrapper: mockFrakProviders,
90
+ });
91
+
92
+ const metadata = {
93
+ logoUrl: "https://example.com/logo.png",
94
+ homepageLink: "https://example.com",
95
+ };
96
+
97
+ result.current.mutate({
98
+ metadata,
99
+ directExit: true,
100
+ });
101
+
102
+ await waitFor(() => {
103
+ expect(result.current.isSuccess).toBe(true);
104
+ });
105
+
106
+ expect(openSso).toHaveBeenCalledWith(
107
+ expect.anything(),
108
+ expect.objectContaining({
109
+ metadata,
110
+ directExit: true,
111
+ })
112
+ );
113
+ });
114
+
115
+ test("should handle mutateAsync", async ({ mockFrakProviders }) => {
116
+ const mockResult = undefined as unknown as OpenSsoReturnType;
117
+
118
+ vi.mocked(openSso).mockResolvedValue(mockResult);
119
+
120
+ const { result } = renderHook(() => useOpenSso(), {
121
+ wrapper: mockFrakProviders,
122
+ });
123
+
124
+ await result.current.mutateAsync({
125
+ directExit: false,
126
+ });
127
+
128
+ await waitFor(() => {
129
+ expect(result.current.isSuccess).toBe(true);
130
+ });
131
+ });
132
+
133
+ test("should handle RPC errors", async ({ mockFrakProviders }) => {
134
+ const error = new Error("SSO open failed");
135
+ vi.mocked(openSso).mockRejectedValue(error);
136
+
137
+ const { result } = renderHook(() => useOpenSso(), {
138
+ wrapper: mockFrakProviders,
139
+ });
140
+
141
+ result.current.mutate({});
142
+
143
+ await waitFor(() => {
144
+ expect(result.current.isError).toBe(true);
145
+ });
146
+
147
+ expect(result.current.error).toEqual(error);
148
+ });
149
+
150
+ test("should handle mutation options", async ({ mockFrakProviders }) => {
151
+ const mockResult = undefined as unknown as OpenSsoReturnType;
152
+
153
+ vi.mocked(openSso).mockResolvedValue(mockResult);
154
+
155
+ const onSuccess = vi.fn();
156
+ const onError = vi.fn();
157
+
158
+ const { result } = renderHook(
159
+ () =>
160
+ useOpenSso({
161
+ mutations: {
162
+ onSuccess,
163
+ onError,
164
+ },
165
+ }),
166
+ {
167
+ wrapper: mockFrakProviders,
168
+ }
169
+ );
170
+
171
+ result.current.mutate({});
172
+
173
+ await waitFor(() => {
174
+ expect(result.current.isSuccess).toBe(true);
175
+ });
176
+
177
+ expect(onSuccess).toHaveBeenCalled();
178
+ expect(onError).not.toHaveBeenCalled();
179
+ });
180
+
181
+ test("should reset mutation state", async ({ mockFrakProviders }) => {
182
+ const mockResult = undefined as unknown as OpenSsoReturnType;
183
+
184
+ vi.mocked(openSso).mockResolvedValue(mockResult);
185
+
186
+ const { result } = renderHook(() => useOpenSso(), {
187
+ wrapper: mockFrakProviders,
188
+ });
189
+
190
+ result.current.mutate({});
191
+
192
+ await waitFor(() => {
193
+ expect(result.current.isSuccess).toBe(true);
194
+ });
195
+
196
+ result.current.reset();
197
+
198
+ await waitFor(() => {
199
+ expect(result.current.isIdle).toBe(true);
200
+ });
201
+ });
202
+ });
@@ -0,0 +1,51 @@
1
+ import type { OpenSsoParamsType, OpenSsoReturnType } from "@frak-labs/core-sdk";
2
+ import { openSso } from "@frak-labs/core-sdk/actions";
3
+ import { ClientNotFound, type FrakRpcError } from "@frak-labs/frame-connector";
4
+ import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
5
+ import { useFrakClient } from "./useFrakClient";
6
+
7
+ /** @ignore */
8
+ type MutationOptions = Omit<
9
+ UseMutationOptions<OpenSsoReturnType, FrakRpcError, OpenSsoParamsType>,
10
+ "mutationFn" | "mutationKey"
11
+ >;
12
+
13
+ /** @inline */
14
+ interface UseOpenSsoParams {
15
+ /**
16
+ * Optional mutation options, see {@link @tanstack/react-query!useMutation | `useMutation()`} for more infos
17
+ */
18
+ mutations?: MutationOptions;
19
+ }
20
+
21
+ /**
22
+ * Hook that return a mutation helping to open the SSO page
23
+ *
24
+ * It's a {@link @tanstack/react-query!home | `tanstack`} wrapper around the {@link @frak-labs/core-sdk!actions.openSso | `openSso()`} action
25
+ *
26
+ * @param args - Optional config object with `mutations` for customizing the underlying {@link @tanstack/react-query!useMutation | `useMutation()`}
27
+ *
28
+ * @group hooks
29
+ *
30
+ * @returns
31
+ * The mutation hook wrapping the `openSso()` action
32
+ * The `mutate` and `mutateAsync` argument is of type {@link @frak-labs/core-sdk!index.OpenSsoParamsType | `OpenSsoParamsType`}
33
+ * The mutation doesn't output any value
34
+ *
35
+ * @see {@link @frak-labs/core-sdk!actions.openSso | `openSso()`} for more info about the underlying action
36
+ * @see {@link @tanstack/react-query!useMutation | `useMutation()`} for more info about the mutation options and response
37
+ */
38
+ export function useOpenSso({ mutations }: UseOpenSsoParams = {}) {
39
+ const client = useFrakClient();
40
+
41
+ return useMutation({
42
+ ...mutations,
43
+ mutationKey: ["frak-sdk", "open-sso"],
44
+ mutationFn: async (params: OpenSsoParamsType) => {
45
+ if (!client) {
46
+ throw new ClientNotFound();
47
+ }
48
+ return openSso(client, params);
49
+ },
50
+ });
51
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Tests for usePrepareSso hook
3
+ * Tests TanStack Query wrapper for preparing SSO URLs
4
+ */
5
+
6
+ import { vi } from "vitest";
7
+
8
+ vi.mock("@frak-labs/core-sdk/actions");
9
+
10
+ import type { PrepareSsoReturnType } from "@frak-labs/core-sdk";
11
+ import { prepareSso } 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 { usePrepareSso } from "./usePrepareSso";
16
+
17
+ describe("usePrepareSso", () => {
18
+ test("should throw ClientNotFound when client is not available", async ({
19
+ queryWrapper,
20
+ }) => {
21
+ const { result } = renderHook(
22
+ () => usePrepareSso({ directExit: true }),
23
+ {
24
+ wrapper: queryWrapper.wrapper,
25
+ }
26
+ );
27
+
28
+ await waitFor(() => {
29
+ expect(result.current.isError).toBe(true);
30
+ });
31
+
32
+ expect(result.current.error).toBeInstanceOf(ClientNotFound);
33
+ });
34
+
35
+ test("should prepare SSO URL successfully", async ({
36
+ mockFrakProviders,
37
+ }) => {
38
+ const mockSsoResult: PrepareSsoReturnType = {
39
+ ssoUrl: "https://wallet-test.frak.id/sso?params=xyz",
40
+ };
41
+
42
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
43
+
44
+ const { result } = renderHook(
45
+ () => usePrepareSso({ directExit: true }),
46
+ {
47
+ wrapper: mockFrakProviders,
48
+ }
49
+ );
50
+
51
+ await waitFor(() => {
52
+ expect(result.current.isSuccess).toBe(true);
53
+ });
54
+
55
+ expect(result.current.data).toEqual(mockSsoResult);
56
+ expect(result.current.data?.ssoUrl).toContain("sso");
57
+ });
58
+
59
+ test("should handle SSO params with redirectUrl", async ({
60
+ mockFrakProviders,
61
+ }) => {
62
+ const mockSsoResult: PrepareSsoReturnType = {
63
+ ssoUrl: "https://wallet-test.frak.id/sso?redirect=example.com",
64
+ };
65
+
66
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
67
+
68
+ const { result } = renderHook(
69
+ () =>
70
+ usePrepareSso({
71
+ redirectUrl: "https://example.com/callback",
72
+ directExit: false,
73
+ }),
74
+ {
75
+ wrapper: mockFrakProviders,
76
+ }
77
+ );
78
+
79
+ await waitFor(() => {
80
+ expect(result.current.isSuccess).toBe(true);
81
+ });
82
+
83
+ expect(result.current.data).toEqual(mockSsoResult);
84
+ expect(prepareSso).toHaveBeenCalledWith(
85
+ expect.anything(),
86
+ expect.objectContaining({
87
+ redirectUrl: "https://example.com/callback",
88
+ directExit: false,
89
+ })
90
+ );
91
+ });
92
+
93
+ test("should handle SSO params with metadata", async ({
94
+ mockFrakProviders,
95
+ }) => {
96
+ const mockSsoResult: PrepareSsoReturnType = {
97
+ ssoUrl: "https://wallet-test.frak.id/sso?metadata=xyz",
98
+ };
99
+
100
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
101
+
102
+ const { result } = renderHook(
103
+ () =>
104
+ usePrepareSso({
105
+ metadata: {
106
+ logoUrl: "https://example.com/logo.png",
107
+ homepageLink: "https://example.com",
108
+ },
109
+ directExit: true,
110
+ }),
111
+ {
112
+ wrapper: mockFrakProviders,
113
+ }
114
+ );
115
+
116
+ await waitFor(() => {
117
+ expect(result.current.isSuccess).toBe(true);
118
+ });
119
+
120
+ expect(result.current.data).toEqual(mockSsoResult);
121
+ });
122
+
123
+ test("should handle RPC errors", async ({ mockFrakProviders }) => {
124
+ const error = new Error("SSO preparation failed");
125
+ vi.mocked(prepareSso).mockRejectedValue(error);
126
+
127
+ const { result } = renderHook(
128
+ () => usePrepareSso({ directExit: true }),
129
+ {
130
+ wrapper: mockFrakProviders,
131
+ }
132
+ );
133
+
134
+ await waitFor(() => {
135
+ expect(result.current.isError).toBe(true);
136
+ });
137
+
138
+ expect(result.current.error).toEqual(error);
139
+ });
140
+
141
+ test("should update query key with params", async ({
142
+ mockFrakProviders,
143
+ }) => {
144
+ const mockSsoResult: PrepareSsoReturnType = {
145
+ ssoUrl: "https://wallet-test.frak.id/sso?params=abc",
146
+ };
147
+
148
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
149
+
150
+ const params = {
151
+ directExit: false,
152
+ redirectUrl: "https://example.com",
153
+ };
154
+
155
+ const { result, rerender } = renderHook(() => usePrepareSso(params), {
156
+ wrapper: mockFrakProviders,
157
+ });
158
+
159
+ await waitFor(() => {
160
+ expect(result.current.isSuccess).toBe(true);
161
+ });
162
+
163
+ expect(result.current.data).toEqual(mockSsoResult);
164
+
165
+ // Query key includes params, so changes should trigger re-fetch
166
+ rerender();
167
+ expect(result.current.data).toEqual(mockSsoResult);
168
+ });
169
+
170
+ test("should call prepareSso with client and params", async ({
171
+ mockFrakProviders,
172
+ mockFrakClient,
173
+ }) => {
174
+ const mockSsoResult: PrepareSsoReturnType = {
175
+ ssoUrl: "https://wallet-test.frak.id/sso?test=123",
176
+ };
177
+
178
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
179
+
180
+ const params = {
181
+ directExit: true,
182
+ metadata: {
183
+ logoUrl: "https://example.com/logo.png",
184
+ },
185
+ };
186
+
187
+ const { result } = renderHook(() => usePrepareSso(params), {
188
+ wrapper: mockFrakProviders,
189
+ });
190
+
191
+ await waitFor(() => {
192
+ expect(result.current.isSuccess).toBe(true);
193
+ });
194
+
195
+ expect(prepareSso).toHaveBeenCalledWith(mockFrakClient, params);
196
+ });
197
+ });
@@ -0,0 +1,55 @@
1
+ import type { PrepareSsoParamsType } from "@frak-labs/core-sdk";
2
+ import { prepareSso } from "@frak-labs/core-sdk/actions";
3
+ import { ClientNotFound } from "@frak-labs/frame-connector";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { useFrakClient } from "./useFrakClient";
6
+
7
+ /**
8
+ * Hook that generates SSO URL for popup flow
9
+ *
10
+ * This is a **synchronous** hook (no async calls) that generates the SSO URL
11
+ * client-side without communicating with the wallet iframe.
12
+ *
13
+ * @param params - SSO parameters for URL generation
14
+ *
15
+ * @group hooks
16
+ *
17
+ * @returns
18
+ * Object containing:
19
+ * - `ssoUrl`: Generated SSO URL (or undefined if client not ready)
20
+ * - `isReady`: Boolean indicating if URL is available
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * function MyComponent() {
25
+ * const { data } = usePrepareSso({
26
+ * metadata: { logoUrl: "..." },
27
+ * directExit: true
28
+ * });
29
+ *
30
+ * const handleClick = () => {
31
+ * if (ssoUrl) {
32
+ * window.open(data?.ssoUrl, "_blank");
33
+ * }
34
+ * };
35
+ *
36
+ * return <button onClick={handleClick} disabled={!isReady}>Login</button>;
37
+ * }
38
+ * ```
39
+ *
40
+ * @see {@link @frak-labs/core-sdk!actions.prepareSso | `prepareSso()`} for the underlying action
41
+ * @see {@link @frak-labs/core-sdk!actions.openSso | `openSso()`} for the recommended high-level API
42
+ */
43
+ export function usePrepareSso(params: PrepareSsoParamsType) {
44
+ const client = useFrakClient();
45
+
46
+ return useQuery({
47
+ queryKey: ["frak-sdk", "prepare-sso", params],
48
+ queryFn: async () => {
49
+ if (!client) {
50
+ throw new ClientNotFound();
51
+ }
52
+ return prepareSso(client, params);
53
+ },
54
+ });
55
+ }