@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.
- package/README.md +25 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +28 -64
- package/dist/index.d.ts +28 -64
- package/dist/index.js +1 -1
- package/package.json +17 -16
- package/src/hook/helper/useReferralInteraction.test.ts +358 -0
- package/src/hook/helper/useReferralInteraction.ts +78 -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,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for FrakConfigProvider
|
|
3
|
+
* Tests that the provider correctly provides config to child components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { render } from "@testing-library/react";
|
|
7
|
+
import React, { createElement } from "react";
|
|
8
|
+
import { describe, expect, test } from "../../tests/vitest-fixtures";
|
|
9
|
+
import { FrakConfigContext, FrakConfigProvider } from "./FrakConfigProvider";
|
|
10
|
+
|
|
11
|
+
describe("FrakConfigProvider", () => {
|
|
12
|
+
test("should render children", ({ mockFrakConfig }) => {
|
|
13
|
+
const { container } = render(
|
|
14
|
+
createElement(
|
|
15
|
+
FrakConfigProvider,
|
|
16
|
+
{ config: mockFrakConfig },
|
|
17
|
+
createElement("div", { "data-testid": "child" }, "Test Child")
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(
|
|
22
|
+
container.querySelector('[data-testid="child"]')
|
|
23
|
+
).toBeInTheDocument();
|
|
24
|
+
expect(container.textContent).toContain("Test Child");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("should provide config to context consumers", ({ mockFrakConfig }) => {
|
|
28
|
+
let receivedConfig: any;
|
|
29
|
+
|
|
30
|
+
const Consumer = () => {
|
|
31
|
+
const config = React.useContext(FrakConfigContext);
|
|
32
|
+
receivedConfig = config;
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
render(
|
|
37
|
+
createElement(
|
|
38
|
+
FrakConfigProvider,
|
|
39
|
+
{ config: mockFrakConfig },
|
|
40
|
+
createElement(Consumer)
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(receivedConfig).toBeDefined();
|
|
45
|
+
expect(receivedConfig.domain).toBe(mockFrakConfig.domain);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("should apply default walletUrl", () => {
|
|
49
|
+
const configWithoutWalletUrl = {
|
|
50
|
+
domain: "example.com",
|
|
51
|
+
metadata: {
|
|
52
|
+
name: "Test App",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
let receivedConfig: any;
|
|
57
|
+
|
|
58
|
+
const Consumer = () => {
|
|
59
|
+
const config = React.useContext(FrakConfigContext);
|
|
60
|
+
receivedConfig = config;
|
|
61
|
+
return null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
render(
|
|
65
|
+
createElement(
|
|
66
|
+
FrakConfigProvider,
|
|
67
|
+
{ config: configWithoutWalletUrl },
|
|
68
|
+
createElement(Consumer)
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(receivedConfig.walletUrl).toBe("https://wallet.frak.id");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("should preserve custom walletUrl", ({ mockFrakConfig }) => {
|
|
76
|
+
let receivedConfig: any;
|
|
77
|
+
|
|
78
|
+
const Consumer = () => {
|
|
79
|
+
const config = React.useContext(FrakConfigContext);
|
|
80
|
+
receivedConfig = config;
|
|
81
|
+
return null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
render(
|
|
85
|
+
createElement(
|
|
86
|
+
FrakConfigProvider,
|
|
87
|
+
{ config: mockFrakConfig },
|
|
88
|
+
createElement(Consumer)
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(receivedConfig.walletUrl).toBe(mockFrakConfig.walletUrl);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("should fallback domain to window.location.host", () => {
|
|
96
|
+
const configWithoutDomain = {
|
|
97
|
+
metadata: {
|
|
98
|
+
name: "Test App",
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
let receivedConfig: any;
|
|
103
|
+
|
|
104
|
+
const Consumer = () => {
|
|
105
|
+
const config = React.useContext(FrakConfigContext);
|
|
106
|
+
receivedConfig = config;
|
|
107
|
+
return null;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
render(
|
|
111
|
+
createElement(
|
|
112
|
+
FrakConfigProvider,
|
|
113
|
+
{ config: configWithoutDomain as any },
|
|
114
|
+
createElement(Consumer)
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// In test environment, window.location.host is "localhost:3000" (JSDOM default)
|
|
119
|
+
expect(receivedConfig.domain).toBe(window.location.host);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("should pass through all config properties", ({ mockFrakConfig }) => {
|
|
123
|
+
let receivedConfig: any;
|
|
124
|
+
|
|
125
|
+
const Consumer = () => {
|
|
126
|
+
const config = React.useContext(FrakConfigContext);
|
|
127
|
+
receivedConfig = config;
|
|
128
|
+
return null;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
render(
|
|
132
|
+
createElement(
|
|
133
|
+
FrakConfigProvider,
|
|
134
|
+
{ config: mockFrakConfig },
|
|
135
|
+
createElement(Consumer)
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(receivedConfig.domain).toBe(mockFrakConfig.domain);
|
|
140
|
+
expect(receivedConfig.walletUrl).toBe(mockFrakConfig.walletUrl);
|
|
141
|
+
expect(receivedConfig.metadata).toEqual(mockFrakConfig.metadata);
|
|
142
|
+
expect(receivedConfig.customizations).toEqual(
|
|
143
|
+
mockFrakConfig.customizations
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("should handle minimal config", () => {
|
|
148
|
+
const minimalConfig = {
|
|
149
|
+
domain: "minimal.com",
|
|
150
|
+
metadata: {
|
|
151
|
+
name: "Minimal Test App",
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
let receivedConfig: any;
|
|
156
|
+
|
|
157
|
+
const Consumer = () => {
|
|
158
|
+
const config = React.useContext(FrakConfigContext);
|
|
159
|
+
receivedConfig = config;
|
|
160
|
+
return null;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
render(
|
|
164
|
+
createElement(
|
|
165
|
+
FrakConfigProvider,
|
|
166
|
+
{ config: minimalConfig },
|
|
167
|
+
createElement(Consumer)
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(receivedConfig.domain).toBe("minimal.com");
|
|
172
|
+
expect(receivedConfig.walletUrl).toBe("https://wallet.frak.id");
|
|
173
|
+
expect(receivedConfig.metadata.name).toBe("Minimal Test App");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("should support nested providers with different configs", () => {
|
|
177
|
+
const outerConfig = {
|
|
178
|
+
domain: "outer.com",
|
|
179
|
+
walletUrl: "https://wallet-outer.frak.id",
|
|
180
|
+
metadata: {
|
|
181
|
+
name: "Outer App",
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const innerConfig = {
|
|
186
|
+
domain: "inner.com",
|
|
187
|
+
walletUrl: "https://wallet-inner.frak.id",
|
|
188
|
+
metadata: {
|
|
189
|
+
name: "Inner App",
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
let outerReceivedConfig: any;
|
|
194
|
+
let innerReceivedConfig: any;
|
|
195
|
+
|
|
196
|
+
const OuterConsumer = () => {
|
|
197
|
+
const config = React.useContext(FrakConfigContext);
|
|
198
|
+
outerReceivedConfig = config;
|
|
199
|
+
return null;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const InnerConsumer = () => {
|
|
203
|
+
const config = React.useContext(FrakConfigContext);
|
|
204
|
+
innerReceivedConfig = config;
|
|
205
|
+
return null;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
render(
|
|
209
|
+
createElement(
|
|
210
|
+
FrakConfigProvider,
|
|
211
|
+
{ config: outerConfig },
|
|
212
|
+
createElement(OuterConsumer),
|
|
213
|
+
createElement(
|
|
214
|
+
FrakConfigProvider,
|
|
215
|
+
{ config: innerConfig },
|
|
216
|
+
createElement(InnerConsumer)
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(outerReceivedConfig.domain).toBe("outer.com");
|
|
222
|
+
expect(innerReceivedConfig.domain).toBe("inner.com");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("should render multiple children", ({ mockFrakConfig }) => {
|
|
226
|
+
const { container } = render(
|
|
227
|
+
createElement(
|
|
228
|
+
FrakConfigProvider,
|
|
229
|
+
{ config: mockFrakConfig },
|
|
230
|
+
createElement("div", { "data-testid": "child1" }, "Child 1"),
|
|
231
|
+
createElement("div", { "data-testid": "child2" }, "Child 2"),
|
|
232
|
+
createElement("div", { "data-testid": "child3" }, "Child 3")
|
|
233
|
+
)
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
expect(
|
|
237
|
+
container.querySelector('[data-testid="child1"]')
|
|
238
|
+
).toBeInTheDocument();
|
|
239
|
+
expect(
|
|
240
|
+
container.querySelector('[data-testid="child2"]')
|
|
241
|
+
).toBeInTheDocument();
|
|
242
|
+
expect(
|
|
243
|
+
container.querySelector('[data-testid="child3"]')
|
|
244
|
+
).toBeInTheDocument();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { FrakWalletSdkConfig } from "@frak-labs/core-sdk";
|
|
2
|
+
import { createContext, createElement, type PropsWithChildren } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The context that will keep the Frak Wallet SDK configuration
|
|
6
|
+
* @ignore
|
|
7
|
+
*/
|
|
8
|
+
export const FrakConfigContext = createContext<FrakWalletSdkConfig | undefined>(
|
|
9
|
+
undefined
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Props to instantiate the Frak Wallet SDK configuration provider
|
|
14
|
+
*
|
|
15
|
+
* @group provider
|
|
16
|
+
*/
|
|
17
|
+
export type FrakConfigProviderProps = {
|
|
18
|
+
/**
|
|
19
|
+
* The wanted Frak configuration
|
|
20
|
+
* @see {@link @frak-labs/core-sdk!index.FrakWalletSdkConfig | FrakWalletSdkConfig}
|
|
21
|
+
*/
|
|
22
|
+
config: FrakWalletSdkConfig;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Simple config provider for the Frak Wallet SDK
|
|
27
|
+
*
|
|
28
|
+
* Should be wrapped within a {@link @tanstack/react-query!QueryClientProvider | `QueryClientProvider`}
|
|
29
|
+
*
|
|
30
|
+
* @group provider
|
|
31
|
+
*
|
|
32
|
+
* @param parameters
|
|
33
|
+
*/
|
|
34
|
+
export function FrakConfigProvider(
|
|
35
|
+
parameters: PropsWithChildren<FrakConfigProviderProps>
|
|
36
|
+
) {
|
|
37
|
+
const { children, config } = parameters;
|
|
38
|
+
return createElement(
|
|
39
|
+
FrakConfigContext.Provider,
|
|
40
|
+
{
|
|
41
|
+
value: {
|
|
42
|
+
...config,
|
|
43
|
+
walletUrl: config.walletUrl ?? "https://wallet.frak.id",
|
|
44
|
+
domain:
|
|
45
|
+
config.domain ??
|
|
46
|
+
(typeof window !== "undefined"
|
|
47
|
+
? window?.location?.host
|
|
48
|
+
: undefined) ??
|
|
49
|
+
"not-found",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
children
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for FrakIFrameClientProvider
|
|
3
|
+
* Tests iframe creation and FrakClient provider
|
|
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
|
+
createIFrameFrakClient: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
import type { FrakClient } from "@frak-labs/core-sdk";
|
|
19
|
+
import { createIFrameFrakClient } from "@frak-labs/core-sdk";
|
|
20
|
+
import { render, waitFor } from "@testing-library/react";
|
|
21
|
+
import { createElement, type ReactNode } from "react";
|
|
22
|
+
import { describe, expect, test } from "../../tests/vitest-fixtures";
|
|
23
|
+
import { FrakConfigProvider } from "./FrakConfigProvider";
|
|
24
|
+
import { FrakIFrameClientProvider } from "./FrakIFrameClientProvider";
|
|
25
|
+
|
|
26
|
+
describe("FrakIFrameClientProvider", () => {
|
|
27
|
+
test("should render iframe with correct src", ({ mockFrakConfig }) => {
|
|
28
|
+
const Wrapper = ({ children }: { children: ReactNode }) =>
|
|
29
|
+
createElement(
|
|
30
|
+
FrakConfigProvider,
|
|
31
|
+
{ config: mockFrakConfig },
|
|
32
|
+
children
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
render(createElement(FrakIFrameClientProvider), {
|
|
36
|
+
wrapper: Wrapper,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const iframe = document.querySelector("iframe");
|
|
40
|
+
expect(iframe).toBeDefined();
|
|
41
|
+
expect(iframe?.src).toContain(`${mockFrakConfig.walletUrl}/listener`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("should apply custom styles to iframe", ({ mockFrakConfig }) => {
|
|
45
|
+
const Wrapper = ({ children }: { children: ReactNode }) =>
|
|
46
|
+
createElement(
|
|
47
|
+
FrakConfigProvider,
|
|
48
|
+
{ config: mockFrakConfig },
|
|
49
|
+
children
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const customStyle = {
|
|
53
|
+
width: "500px",
|
|
54
|
+
height: "600px",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
render(
|
|
58
|
+
createElement(FrakIFrameClientProvider, { style: customStyle }),
|
|
59
|
+
{
|
|
60
|
+
wrapper: Wrapper,
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const iframe = document.querySelector("iframe");
|
|
65
|
+
expect(iframe?.style.width).toBe("500px");
|
|
66
|
+
expect(iframe?.style.height).toBe("600px");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("should create FrakClient when iframe ref is set", async ({
|
|
70
|
+
mockFrakConfig,
|
|
71
|
+
}) => {
|
|
72
|
+
const Wrapper = ({ children }: { children: ReactNode }) =>
|
|
73
|
+
createElement(
|
|
74
|
+
FrakConfigProvider,
|
|
75
|
+
{ config: mockFrakConfig },
|
|
76
|
+
children
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const mockClient = { config: mockFrakConfig } as FrakClient;
|
|
80
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(mockClient);
|
|
81
|
+
|
|
82
|
+
render(createElement(FrakIFrameClientProvider), {
|
|
83
|
+
wrapper: Wrapper,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
expect(createIFrameFrakClient).toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should not recreate client if already exists", async ({
|
|
92
|
+
mockFrakConfig,
|
|
93
|
+
}) => {
|
|
94
|
+
const Wrapper = ({ children }: { children: ReactNode }) =>
|
|
95
|
+
createElement(
|
|
96
|
+
FrakConfigProvider,
|
|
97
|
+
{ config: mockFrakConfig },
|
|
98
|
+
children
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const mockClient = { config: mockFrakConfig } as FrakClient;
|
|
102
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(mockClient);
|
|
103
|
+
|
|
104
|
+
const { rerender } = render(createElement(FrakIFrameClientProvider), {
|
|
105
|
+
wrapper: Wrapper,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await waitFor(() => {
|
|
109
|
+
expect(createIFrameFrakClient).toHaveBeenCalledTimes(1);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Rerender the component
|
|
113
|
+
rerender(createElement(FrakIFrameClientProvider));
|
|
114
|
+
|
|
115
|
+
// Should still only be called once
|
|
116
|
+
expect(createIFrameFrakClient).toHaveBeenCalledTimes(1);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("should render without children", ({ mockFrakConfig }) => {
|
|
120
|
+
const Wrapper = ({ children }: { children: ReactNode }) =>
|
|
121
|
+
createElement(
|
|
122
|
+
FrakConfigProvider,
|
|
123
|
+
{ config: mockFrakConfig },
|
|
124
|
+
children
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const { container } = render(createElement(FrakIFrameClientProvider), {
|
|
128
|
+
wrapper: Wrapper,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const iframe = container.querySelector("iframe");
|
|
132
|
+
expect(iframe).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("should use baseIframeProps for iframe attributes", ({
|
|
136
|
+
mockFrakConfig,
|
|
137
|
+
}) => {
|
|
138
|
+
const Wrapper = ({ children }: { children: ReactNode }) =>
|
|
139
|
+
createElement(
|
|
140
|
+
FrakConfigProvider,
|
|
141
|
+
{ config: mockFrakConfig },
|
|
142
|
+
children
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
render(createElement(FrakIFrameClientProvider), {
|
|
146
|
+
wrapper: Wrapper,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const iframe = document.querySelector("iframe");
|
|
150
|
+
expect(iframe).toBeDefined();
|
|
151
|
+
// baseIframeProps should set these attributes
|
|
152
|
+
expect(iframe?.getAttribute("sandbox")).toBeDefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("should handle iframe ref callback correctly", ({
|
|
156
|
+
mockFrakConfig,
|
|
157
|
+
}) => {
|
|
158
|
+
const Wrapper = ({ children }: { children: ReactNode }) =>
|
|
159
|
+
createElement(
|
|
160
|
+
FrakConfigProvider,
|
|
161
|
+
{ config: mockFrakConfig },
|
|
162
|
+
children
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const mockClient = { config: mockFrakConfig } as FrakClient;
|
|
166
|
+
let callCount = 0;
|
|
167
|
+
|
|
168
|
+
vi.mocked(createIFrameFrakClient).mockImplementation(() => {
|
|
169
|
+
callCount++;
|
|
170
|
+
return mockClient;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
render(createElement(FrakIFrameClientProvider), {
|
|
174
|
+
wrapper: Wrapper,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Client creation should be called at most once
|
|
178
|
+
expect(callCount).toBeLessThanOrEqual(1);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("should pass config to createIFrameFrakClient", async ({
|
|
182
|
+
mockFrakConfig,
|
|
183
|
+
}) => {
|
|
184
|
+
const Wrapper = ({ children }: { children: ReactNode }) =>
|
|
185
|
+
createElement(
|
|
186
|
+
FrakConfigProvider,
|
|
187
|
+
{ config: mockFrakConfig },
|
|
188
|
+
children
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const mockClient = { config: mockFrakConfig } as FrakClient;
|
|
192
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(mockClient);
|
|
193
|
+
|
|
194
|
+
render(createElement(FrakIFrameClientProvider), {
|
|
195
|
+
wrapper: Wrapper,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(createIFrameFrakClient).toHaveBeenCalledWith(
|
|
200
|
+
expect.objectContaining({
|
|
201
|
+
config: expect.objectContaining({
|
|
202
|
+
domain: "example.com",
|
|
203
|
+
walletUrl: "https://wallet-test.frak.id",
|
|
204
|
+
}),
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
baseIframeProps,
|
|
3
|
+
createIFrameFrakClient,
|
|
4
|
+
type FrakClient,
|
|
5
|
+
type FrakWalletSdkConfig,
|
|
6
|
+
} from "@frak-labs/core-sdk";
|
|
7
|
+
import {
|
|
8
|
+
type CSSProperties,
|
|
9
|
+
createContext,
|
|
10
|
+
createElement,
|
|
11
|
+
Fragment,
|
|
12
|
+
type ReactNode,
|
|
13
|
+
useState,
|
|
14
|
+
} from "react";
|
|
15
|
+
import { useFrakConfig } from "../hook";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The context that will keep the Frak Wallet SDK client
|
|
19
|
+
* @ignore
|
|
20
|
+
*/
|
|
21
|
+
export const FrakIFrameClientContext = createContext<FrakClient | undefined>(
|
|
22
|
+
undefined
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Props to instantiate the Frak Wallet SDK configuration provider
|
|
27
|
+
*
|
|
28
|
+
* @group provider
|
|
29
|
+
*/
|
|
30
|
+
export type FrakIFrameClientProps = {
|
|
31
|
+
config: FrakWalletSdkConfig;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* IFrame client provider for the Frak Wallet SDK
|
|
36
|
+
* It will automatically create the frak wallet iFrame (required for the wallet to communicate with the SDK securely), and provide it in the context
|
|
37
|
+
*
|
|
38
|
+
* @group provider
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* This provider must be wrapped within a {@link FrakConfigProvider} to work properly
|
|
42
|
+
*
|
|
43
|
+
* @param args
|
|
44
|
+
* @param args.style - Some custom styles to apply to the iFrame
|
|
45
|
+
* @param args.children - Descedant components that will have access to the Frak Client
|
|
46
|
+
*/
|
|
47
|
+
export function FrakIFrameClientProvider({
|
|
48
|
+
style,
|
|
49
|
+
children,
|
|
50
|
+
}: {
|
|
51
|
+
style?: CSSProperties;
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
}) {
|
|
54
|
+
const config = useFrakConfig();
|
|
55
|
+
|
|
56
|
+
// Using a state for the client since using directly a client built inside the ref cause re-render loop
|
|
57
|
+
const [client, setClient] = useState<FrakClient | undefined>(undefined);
|
|
58
|
+
|
|
59
|
+
// Create the iframe that will be used to communicate with the wallet
|
|
60
|
+
const iFrame = createElement("iframe", {
|
|
61
|
+
...baseIframeProps,
|
|
62
|
+
src: `${config.walletUrl}/listener`,
|
|
63
|
+
style: style ?? baseIframeProps.style,
|
|
64
|
+
ref: (iframe: HTMLIFrameElement) => {
|
|
65
|
+
if (!iframe || client) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
setClient(
|
|
69
|
+
createIFrameFrakClient({
|
|
70
|
+
iframe,
|
|
71
|
+
config,
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Create the component that will provide the client
|
|
78
|
+
const providerComponent = createElement(
|
|
79
|
+
FrakIFrameClientContext.Provider,
|
|
80
|
+
{ value: client },
|
|
81
|
+
children
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Return both components
|
|
85
|
+
return createElement(Fragment, null, iFrame, providerComponent);
|
|
86
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { FrakConfigProviderProps } from "./FrakConfigProvider";
|
|
2
|
+
export { FrakConfigContext, FrakConfigProvider } from "./FrakConfigProvider";
|
|
3
|
+
export type { FrakIFrameClientProps } from "./FrakIFrameClientProvider";
|
|
4
|
+
export {
|
|
5
|
+
FrakIFrameClientContext,
|
|
6
|
+
FrakIFrameClientProvider,
|
|
7
|
+
} from "./FrakIFrameClientProvider";
|