zh-web-sdk 2.16.1 → 2.17.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 (51) hide show
  1. package/README.md +225 -32
  2. package/package.json +6 -1
  3. package/.eslintrc.js +0 -12
  4. package/.github/CHANGELOG_TEMPLATE.md +0 -73
  5. package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
  6. package/.github/backup/publish-tag.yaml +0 -14
  7. package/.github/workflows/build.yaml +0 -13
  8. package/.github/workflows/pr.yaml +0 -14
  9. package/.github/workflows/publish.yaml +0 -13
  10. package/.github/workflows/security.yml +0 -15
  11. package/.github/workflows/tag.yaml +0 -14
  12. package/RELEASING.md +0 -39
  13. package/jest.config.js +0 -8
  14. package/jest.setup.js +0 -24
  15. package/scripts/build.js +0 -34
  16. package/scripts/zip.js +0 -49
  17. package/src/__tests__/jwt-auth-detection.test.ts +0 -96
  18. package/src/api/convert-token.ts +0 -23
  19. package/src/constants.ts +0 -2
  20. package/src/hooks/__tests__/use-window-size.test.tsx +0 -26
  21. package/src/hooks/use-window-size.ts +0 -19
  22. package/src/iframe-container/AppContainer.tsx +0 -495
  23. package/src/iframe-container/__tests__/AppContainer.test.tsx +0 -300
  24. package/src/iframe-container/hooks/__tests__/use-style-updates.test.ts +0 -430
  25. package/src/iframe-container/hooks/use-style-updates.ts +0 -82
  26. package/src/index.tsx +0 -645
  27. package/src/redux/actions/index.ts +0 -27
  28. package/src/redux/reducers/constants.ts +0 -10
  29. package/src/redux/reducers/crypto-account-link-payouts.ts +0 -60
  30. package/src/redux/reducers/crypto-account-link.ts +0 -60
  31. package/src/redux/reducers/crypto-buy.ts +0 -75
  32. package/src/redux/reducers/crypto-sell.ts +0 -64
  33. package/src/redux/reducers/crypto-withdrawals.ts +0 -64
  34. package/src/redux/reducers/fiat-account-link.ts +0 -60
  35. package/src/redux/reducers/fiat-deposits.ts +0 -64
  36. package/src/redux/reducers/fiat-withdrawals.ts +0 -64
  37. package/src/redux/reducers/fund.ts +0 -75
  38. package/src/redux/reducers/index.ts +0 -35
  39. package/src/redux/reducers/onboarding.ts +0 -74
  40. package/src/redux/reducers/pay.ts +0 -64
  41. package/src/redux/reducers/payouts.ts +0 -64
  42. package/src/redux/reducers/profile.ts +0 -63
  43. package/src/redux/store/index.ts +0 -10
  44. package/src/styles.ts +0 -108
  45. package/src/types.ts +0 -578
  46. package/src/utils/auth-to-fund-mapper.ts +0 -174
  47. package/src/utils/strings.ts +0 -19
  48. package/src/utils/test-utils.tsx +0 -36
  49. package/src/utils/world-app-utils.ts +0 -8
  50. package/src/utils.ts +0 -27
  51. package/tsconfig.json +0 -26
@@ -1,96 +0,0 @@
1
- import { decodeJWT } from "../api/convert-token";
2
-
3
- describe("JWT Auth Detection", () => {
4
- describe("decodeJWT", () => {
5
- it("should decode a valid JWT with Connect issuer", () => {
6
- // JWT with iss: "https://connect-xyz.us.auth0.com/"
7
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2Nvbm5lY3QueHl6L29yZ2FuaXphdGlvbiI6eyJpZCI6ImY0NDZmMTRjLTQwMzUtNDEwNS04OTJiLTE2NDliZmYxNjQyNCJ9LCJodHRwczovL2Nvbm5lY3QueHl6L2FjY291bnQiOnsiaWQiOiI1MzVhNzEyYS0wZmE0LTRjNmMtYjc4Zi01M2NiYTM1NmRkY2QiLCJyZWZlcmVuY2VJZCI6InJlZl8xNzY4MjYzNjg1NTM2X3ExM2tmdXc2ciJ9LCJodHRwczovL2Nvbm5lY3QueHl6L3VzZXIiOnsiaWQiOiJjZWU5ZmMzZS1lNjRmLTQ1OTItODQzZS1kN2ZhNzY2OWVmNGYifSwiaHR0cHM6Ly9jb25uZWN0Lnh5ei9zZXNzaW9uIjp7ImNyZWF0ZWRBdCI6IjIwMjYtMDEtMTNUMDA6MjE6MjZaIiwiaWQiOiI2MGQzZjkyYS1kNDlhLTQzM2EtOGU2OC1iMmZiNjE5ZDlmNTAiLCJtZXRhZGF0YSI6eyJ0ZXN0IjoidGVzdCJ9LCJzY29wZXMiOlsidXNlcjpkZXBvc2l0OnNlbmQiXSwic2V0dGluZ3MiOnsiemVyb19oYXNoIjp7ImVuYWJsZWQiOmZhbHNlfX19LCJpc3MiOiJodHRwczovL2Nvbm5lY3QteHl6LnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJuTHBXSjU3U0Foa2JIODFST1ZKT0p3WVVYeDE5cjdYTkBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly91c2Vycy5jZXJ0LmNvbm5lY3QueHl6IiwiaWF0IjoxNzY4MjYzNjg2LCJleHAiOjE3NjgzNTAwODYsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyIsImF6cCI6Im5McFdKNTdTQWhrYkg4MVJPVkpPSndZVVh4MTlyN1hOIn0.test";
8
-
9
- const payload = decodeJWT(jwt);
10
-
11
- expect(payload).toBeDefined();
12
- expect(payload?.iss).toBe("https://connect-xyz.us.auth0.com/");
13
- expect(payload?.iss).toContain("connect");
14
- });
15
-
16
- it("should decode a valid JWT without Connect issuer", () => {
17
- // JWT with iss: "https://zerohash.com/"
18
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3plcm9oYXNoLmNvbS8iLCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.test";
19
-
20
- const payload = decodeJWT(jwt);
21
-
22
- expect(payload).toBeDefined();
23
- expect(payload?.iss).toBe("https://zerohash.com/");
24
- expect(payload?.iss).not.toContain("connect");
25
- });
26
-
27
- it("should return null for invalid JWT format", () => {
28
- const invalidJwt = "invalid.jwt";
29
-
30
- const payload = decodeJWT(invalidJwt);
31
-
32
- expect(payload).toBeNull();
33
- });
34
-
35
- it("should return null for malformed JWT", () => {
36
- const malformedJwt = "header.payload.signature.extra";
37
-
38
- const payload = decodeJWT(malformedJwt);
39
-
40
- expect(payload).toBeNull();
41
- });
42
- });
43
-
44
- describe("useAuth determination logic", () => {
45
- it("should determine useAuth=true when JWT has Connect issuer", () => {
46
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Nvbm5lY3QteHl6LnVzLmF1dGgwLmNvbS8ifQ.test";
47
- const payload = decodeJWT(jwt);
48
-
49
- const useAuth = payload && payload.iss && typeof payload.iss === 'string' && payload.iss.includes('connect');
50
-
51
- expect(useAuth).toBe(true);
52
- });
53
-
54
- it("should determine useAuth=false when JWT does not have Connect issuer", () => {
55
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3plcm9oYXNoLmNvbS8ifQ.test";
56
- const payload = decodeJWT(jwt);
57
-
58
- const useAuth = payload && payload.iss && typeof payload.iss === 'string' && payload.iss.includes('connect');
59
-
60
- expect(useAuth).toBe(false);
61
- });
62
-
63
- it("should determine useAuth=false when JWT has no issuer", () => {
64
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test";
65
- const payload = decodeJWT(jwt);
66
-
67
- const useAuth = payload && payload.iss && typeof payload.iss === 'string' && payload.iss.includes('connect');
68
-
69
- expect(useAuth).toBeFalsy();
70
- });
71
-
72
- it("should determine useAuth=false when JWT is invalid", () => {
73
- const jwt = "invalid";
74
- const payload = decodeJWT(jwt);
75
-
76
- const useAuth = payload && payload.iss && typeof payload.iss === 'string' && payload.iss.includes('connect');
77
-
78
- expect(useAuth).toBeFalsy();
79
- });
80
-
81
- it("should match Connect issuer variations", () => {
82
- const testCases = [
83
- { jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Nvbm5lY3QteHl6LnVzLmF1dGgwLmNvbS8ifQ.test", expected: true },
84
- { jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Nvbm5lY3QteHl6LmV1LmF1dGgwLmNvbS8ifQ.test", expected: true },
85
- { jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Nvbm5lY3QueHl6L2FwaS8ifQ.test", expected: true },
86
- { jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3plcm9oYXNoLmNvbS8ifQ.test", expected: false },
87
- ];
88
-
89
- testCases.forEach(({ jwt, expected }) => {
90
- const payload = decodeJWT(jwt);
91
- const useAuth = payload && payload.iss && typeof payload.iss === 'string' && payload.iss.includes('connect');
92
- expect(useAuth).toBe(expected);
93
- });
94
- });
95
- });
96
- });
@@ -1,23 +0,0 @@
1
- export interface JWTPayload {
2
- auth_embedded?: boolean;
3
- reference_id?: string;
4
- [key: string]: unknown;
5
- }
6
-
7
- export function decodeJWT(token: string): JWTPayload | null {
8
- try {
9
- const parts = token.split('.');
10
- if (parts.length !== 3) {
11
- console.error('[zh-web-sdk] Invalid JWT format');
12
- return null;
13
- }
14
-
15
- const payload = parts[1];
16
- const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
17
- return JSON.parse(decoded);
18
- } catch (error) {
19
- console.error('[zh-web-sdk] Error decoding JWT:', error);
20
- return null;
21
- }
22
- }
23
-
package/src/constants.ts DELETED
@@ -1,2 +0,0 @@
1
- export const DEFAULT_ZH_APPS_URL = "https://web-sdk.zerohash.com/";
2
- export const DEFAULT_ROOT_ID_PREFIX = "zerohash"
@@ -1,26 +0,0 @@
1
- import {useWindowSize} from "../use-window-size";
2
- import {act, renderHook, waitFor} from "@testing-library/react";
3
-
4
- describe("useWindowSize", () => {
5
- it("Returns correct window dimensions on redirect", async () => {
6
- const { result } = renderHook(() => useWindowSize());
7
-
8
- // Asset initial values.
9
- expect(result.current.height).toBe(768);
10
- expect(result.current.width).toBe(1024);
11
-
12
-
13
- // Change the viewport to 500px.
14
- global.innerWidth = 500;
15
- global.innerHeight = 500;
16
-
17
- act(() => {
18
- global.dispatchEvent(new Event('resize'));
19
- })
20
-
21
- await waitFor(() => {
22
- expect(result.current.width).toBe(500);
23
- expect(result.current.height).toBe(500);
24
- })
25
- })
26
- })
@@ -1,19 +0,0 @@
1
- import {useLayoutEffect, useState} from "react";
2
-
3
- export const useWindowSize = () => {
4
- const [height, setHeight] = useState(0);
5
- const [width, setWidth] = useState(0);
6
- useLayoutEffect(() => {
7
- function updateSize() {
8
- setWidth(window.innerWidth);
9
- setHeight(window.innerHeight);
10
- }
11
- window.addEventListener('resize', updateSize);
12
- updateSize();
13
- return () => window.removeEventListener('resize', updateSize);
14
- }, []);
15
- return {
16
- height,
17
- width
18
- };
19
- }
@@ -1,495 +0,0 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
- import { connect, useSelector } from "react-redux";
3
- import { Auth } from "@connect-xyz/auth-react";
4
- import { createAuthToFundCallbacks } from "../utils/auth-to-fund-mapper";
5
-
6
- import {
7
- containerMediaStyles,
8
- containerStyle,
9
- minWidthMatcher,
10
- screenSizes,
11
- appWrapperStyle as defaultAppWrapperStyle,
12
- modalStyle as defaultModalStyle,
13
- iframeWrapperStyle as defaultIframeWrapperStyle,
14
- iframeStyle as defaultIframeStyle,
15
- } from "../styles";
16
- import { closeModal } from "../redux/actions";
17
- import {
18
- AppIdentifier,
19
- appIdentifierToActionPrefixMap,
20
- AuthSettings,
21
- Filters,
22
- IncomingMessageType,
23
- OnboardingPage,
24
- Page,
25
- } from "../types";
26
- import { useWindowSize } from "../hooks/use-window-size";
27
- import { isInsideWorldApp } from "../utils/world-app-utils";
28
- import {
29
- PayCommandInput,
30
- Tokens,
31
- tokenToDecimals,
32
- } from "@worldcoin/minikit-js";
33
- import { useStyleUpdates } from "./hooks/use-style-updates";
34
-
35
- interface AppContainerProps {
36
- isAppActive?: boolean;
37
- isAppLoaded?: boolean;
38
- jwt?: string;
39
- zeroHashAppURL: string;
40
- appIdentifier: AppIdentifier;
41
- filters?: Filters;
42
- navigate?: Page;
43
- useAuth?: boolean;
44
- authSettings?: AuthSettings;
45
- }
46
-
47
- interface AppContainerMappedProps {
48
- [AppIdentifier.CRYPTO_WITHDRAWALS]: {
49
- isAppActive: boolean;
50
- isAppLoaded: boolean;
51
- jwt: string;
52
- filters?: Filters;
53
- };
54
- [AppIdentifier.ONBOARDING]: {
55
- isAppActive: boolean;
56
- isAppLoaded: boolean;
57
- jwt: string;
58
- filters?: Filters;
59
- navigate?: OnboardingPage;
60
- };
61
- [AppIdentifier.FIAT_DEPOSITS]: {
62
- isAppActive: boolean;
63
- isAppLoaded: boolean;
64
- jwt: string;
65
- filters?: Filters;
66
- };
67
- [AppIdentifier.FIAT_WITHDRAWALS]: {
68
- isAppActive: boolean;
69
- isAppLoaded: boolean;
70
- jwt: string;
71
- filters?: Filters;
72
- };
73
- [AppIdentifier.CRYPTO_BUY]: {
74
- isAppActive: boolean;
75
- isAppLoaded: boolean;
76
- jwt: string;
77
- filters?: Filters;
78
- };
79
- [AppIdentifier.CRYPTO_SELL]: {
80
- isAppActive: boolean;
81
- isAppLoaded: boolean;
82
- jwt: string;
83
- filters?: Filters;
84
- };
85
- [AppIdentifier.CSP_CRYPTO_WITHDRAWALS]: {
86
- isAppActive: boolean;
87
- isAppLoaded: boolean;
88
- jwt: string;
89
- filters?: Filters;
90
- };
91
- [AppIdentifier.CSP_FIAT_WITHDRAWALS]: {
92
- isAppActive: boolean;
93
- isAppLoaded: boolean;
94
- jwt: string;
95
- filters?: Filters;
96
- };
97
- [AppIdentifier.CSP_CRYPTO_SELL]: {
98
- isAppActive: boolean;
99
- isAppLoaded: boolean;
100
- jwt: string;
101
- filters?: Filters;
102
- };
103
- [AppIdentifier.FUND]: {
104
- isAppActive: boolean;
105
- isAppLoaded: boolean;
106
- jwt: string;
107
- filters?: Filters;
108
- useAuth?: boolean;
109
- authSettings?: AuthSettings;
110
- };
111
- [AppIdentifier.PROFILE]: {
112
- isAppActive: boolean;
113
- isAppLoaded: boolean;
114
- jwt: string;
115
- filters?: Filters;
116
- };
117
- [AppIdentifier.CRYPTO_ACCOUNT_LINK]: {
118
- isAppActive: boolean;
119
- isAppLoaded: boolean;
120
- jwt: string;
121
- filters?: Filters;
122
- };
123
- [AppIdentifier.CRYPTO_ACCOUNT_LINK_PAYOUTS]: {
124
- isAppActive: boolean;
125
- isAppLoaded: boolean;
126
- jwt: string;
127
- filters?: Filters;
128
- };
129
- [AppIdentifier.PAYOUTS]: {
130
- isAppActive: boolean;
131
- isAppLoaded: boolean;
132
- jwt: string;
133
- filters?: Filters;
134
- };
135
- [AppIdentifier.PAY]: {
136
- isAppActive: boolean;
137
- isAppLoaded: boolean;
138
- jwt: string;
139
- filters?: Filters;
140
- };
141
- [AppIdentifier.FIAT_ACCOUNT_LINK]: {
142
- isAppActive: boolean;
143
- isAppLoaded: boolean;
144
- jwt: string;
145
- filters?: Filters;
146
- };
147
- }
148
-
149
- const appLoadTime = Date.now();
150
-
151
- const mapAppToTitle = {
152
- [AppIdentifier.ONBOARDING]: "Onboarding",
153
- [AppIdentifier.CRYPTO_WITHDRAWALS]: "Crypto Withdrawals",
154
- [AppIdentifier.FIAT_DEPOSITS]: "Fiat Deposits",
155
- [AppIdentifier.FIAT_WITHDRAWALS]: "Fiat Withdrawals",
156
- [AppIdentifier.CRYPTO_BUY]: "Crypto Buy",
157
- [AppIdentifier.CRYPTO_SELL]: "Crypto Sell",
158
- [AppIdentifier.CSP_CRYPTO_WITHDRAWALS]: "CBP Crypto Withdrawals",
159
- [AppIdentifier.CSP_FIAT_WITHDRAWALS]: "CBP Fiat Withdrawals",
160
- [AppIdentifier.CSP_CRYPTO_SELL]: "Crypto Sell",
161
- [AppIdentifier.FUND]: "Fund",
162
- [AppIdentifier.PROFILE]: "Profile",
163
- [AppIdentifier.CRYPTO_ACCOUNT_LINK]: "Crypto Account Link",
164
- [AppIdentifier.CRYPTO_ACCOUNT_LINK_PAYOUTS]: "Crypto Account Link Payouts",
165
- [AppIdentifier.PAYOUTS]: "Payouts",
166
- [AppIdentifier.PAY]: "Pay",
167
- [AppIdentifier.FIAT_ACCOUNT_LINK]: "Fiat Account Link",
168
- };
169
-
170
- const AppContainer = ({
171
- isAppActive,
172
- isAppLoaded,
173
- jwt,
174
- zeroHashAppURL,
175
- appIdentifier,
176
- navigate,
177
- useAuth,
178
- authSettings,
179
- }: AppContainerProps) => {
180
- const title = mapAppToTitle[appIdentifier];
181
- const hasJwt = !!jwt;
182
- const [container, setContainerStyle] = useState(containerStyle);
183
- const allAppsState = useSelector((state) => state as AppContainerMappedProps);
184
- const { height } = useWindowSize();
185
-
186
- // Styles received from iframe via postMessage
187
- const { styles, stylesLoaded, handleStyleConfig } = useStyleUpdates(
188
- zeroHashAppURL,
189
- {
190
- appWrapperStyle: defaultAppWrapperStyle,
191
- modalStyle: defaultModalStyle,
192
- iframeWrapperStyle: defaultIframeWrapperStyle,
193
- iframeStyle: defaultIframeStyle,
194
- },
195
- );
196
-
197
- // Determine environment from zeroHashAppURL
198
- const authEnv =
199
- zeroHashAppURL.includes("sandbox") || zeroHashAppURL.includes("cert")
200
- ? "sandbox"
201
- : "production";
202
-
203
- /**
204
- * setContainer sets the CSS styles based on the current matching media queries
205
- */
206
- const setContainer = () => {
207
- const sizeId = minWidthMatcher();
208
- const containerMediaStyle = containerMediaStyles[sizeId];
209
- setContainerStyle({ ...containerStyle, ...containerMediaStyle });
210
- };
211
-
212
- const iRef: React.MutableRefObject<HTMLIFrameElement | null> =
213
- useRef<HTMLIFrameElement | null>(null);
214
- useEffect(() => {
215
- // Send events to the iframe when Apps are ready
216
- if (
217
- zeroHashAppURL &&
218
- isAppLoaded &&
219
- hasJwt &&
220
- iRef.current?.contentWindow
221
- ) {
222
- // This loops all registered apps and send events listed below.
223
- Object.keys(allAppsState).forEach((key: string) => {
224
- if (!allAppsState[key as AppIdentifier].jwt) return;
225
-
226
- // Send JWT token
227
- iRef.current?.contentWindow?.postMessage(
228
- {
229
- type: `${appIdentifierToActionPrefixMap.get(
230
- key as AppIdentifier,
231
- )}SEND_JWT_TOKEN`,
232
- jwt: allAppsState[key as AppIdentifier].jwt,
233
- },
234
- zeroHashAppURL,
235
- );
236
-
237
- // If the zh-web-sdk is being run inside the WorldApp, we must send the WorldApp object
238
- // to the iframe.
239
- if (isInsideWorldApp()) {
240
- iRef.current?.contentWindow?.postMessage(
241
- { type: `WORLD_APP_OBJECT`, WorldApp: window.WorldApp },
242
- zeroHashAppURL,
243
- );
244
- }
245
-
246
- // Send filters
247
- if (allAppsState[key as AppIdentifier].filters) {
248
- iRef.current?.contentWindow?.postMessage(
249
- {
250
- type: `${appIdentifierToActionPrefixMap.get(
251
- key as AppIdentifier,
252
- )}SEND_FILTERS`,
253
- filters: allAppsState[key as AppIdentifier].filters,
254
- },
255
- zeroHashAppURL,
256
- );
257
- }
258
- // Send navigate to Onboarding App
259
- if (appIdentifier === AppIdentifier.ONBOARDING && navigate) {
260
- iRef.current?.contentWindow?.postMessage(
261
- {
262
- type: `${appIdentifierToActionPrefixMap.get(
263
- AppIdentifier.ONBOARDING,
264
- )}NAVIGATE`,
265
- navigate,
266
- },
267
- zeroHashAppURL,
268
- );
269
- }
270
- });
271
- }
272
- }, [
273
- appIdentifier,
274
- hasJwt,
275
- isAppLoaded,
276
- jwt,
277
- zeroHashAppURL,
278
- allAppsState,
279
- navigate,
280
- ]);
281
-
282
- useEffect(() => {
283
- // set the styles when the screen size changes
284
- setContainer();
285
- if (window) {
286
- screenSizes.forEach(({ size }) => {
287
- window
288
- .matchMedia(`(min-width: ${size}px)`)
289
- .addEventListener("change", setContainer);
290
- });
291
- return () =>
292
- screenSizes.forEach(({ size }) => {
293
- window
294
- .matchMedia(`(min-width: ${size}px)`)
295
- .removeEventListener("change", setContainer);
296
- });
297
- }
298
- }, []);
299
-
300
- /**
301
- * Handles MiniKit pay command processing
302
- */
303
- const handleMiniKitPayCommand = useCallback(
304
- async (event: MessageEvent) => {
305
- const zhAppsURL = new URL(zeroHashAppURL);
306
- iRef.current?.contentWindow?.postMessage(
307
- {
308
- type: "MINIKIT_PAY_COMMAND_RECEIVED",
309
- payload: {
310
- origin: event.origin,
311
- zhAppsURLOrigin: zhAppsURL.origin,
312
- data: event.data,
313
- },
314
- },
315
- zeroHashAppURL,
316
- );
317
-
318
- if (!window.MiniKit) {
319
- iRef.current?.contentWindow?.postMessage(
320
- {
321
- type: "MINIKIT_PAY_COMMAND_CANCELED",
322
- payload: { reason: "window.Minikit not found" },
323
- },
324
- zeroHashAppURL,
325
- );
326
- return;
327
- }
328
-
329
- try {
330
- if (event.data?.type === "MINIKIT_PAY_COMMAND") {
331
- const payload = event.data.payload;
332
- const minikitPayload: PayCommandInput = {
333
- reference: payload.reference,
334
- to: payload.to,
335
- tokens: [
336
- {
337
- symbol: payload.token as Tokens,
338
- token_amount: tokenToDecimals(
339
- Number(payload.amount),
340
- payload.token,
341
- ).toString(),
342
- },
343
- ],
344
- description: payload.description,
345
- };
346
- iRef.current?.contentWindow?.postMessage(
347
- {
348
- type: "MINIKIT_PAY_COMMAND_SUBMITTING_PAYMENT",
349
- payload: { minikitPayload },
350
- },
351
- zeroHashAppURL,
352
- );
353
- const { finalPayload } =
354
- await window.MiniKit.commandsAsync.pay(minikitPayload);
355
-
356
- iRef.current?.contentWindow?.postMessage(
357
- { type: "MINIKIT_PAY_COMMAND_RESULT", payload: { finalPayload } },
358
- zeroHashAppURL,
359
- );
360
- }
361
- } catch (error) {
362
- iRef.current?.contentWindow?.postMessage(
363
- {
364
- type: "MINIKIT_PAY_COMMAND_ERROR",
365
- payload: { error, status: "error" },
366
- },
367
- zeroHashAppURL,
368
- );
369
- }
370
- },
371
- [zeroHashAppURL],
372
- );
373
-
374
- /**
375
- * Main postMessage event handler that routes messages to appropriate handlers
376
- */
377
- const postMessageEventHandler = useCallback(
378
- async (event: MessageEvent) => {
379
- // Handle style configuration messages
380
- if (event.data.type === IncomingMessageType.StyleConfig) {
381
- handleStyleConfig(event.data, event.origin);
382
- return;
383
- }
384
-
385
- // Handle MiniKit pay commands
386
- await handleMiniKitPayCommand(event);
387
- },
388
- [handleStyleConfig, handleMiniKitPayCommand],
389
- );
390
-
391
- useEffect(() => {
392
- window.addEventListener("message", postMessageEventHandler);
393
- return () => window.removeEventListener("message", postMessageEventHandler);
394
- }, [postMessageEventHandler, zeroHashAppURL]);
395
-
396
- const iframeURL = new URL(zeroHashAppURL);
397
- iframeURL.searchParams.set("name", appLoadTime.toString());
398
- if (window) {
399
- iframeURL.searchParams.set("origin", window.location.origin);
400
- }
401
-
402
- if (!isAppActive) {
403
- return <></>;
404
- }
405
- const { appWrapperStyle, modalStyle, iframeWrapperStyle, iframeStyle } =
406
- styles;
407
-
408
- // If useAuth is true, render Auth component instead of iframe
409
- if (useAuth && hasJwt) {
410
- const authCallbacks = createAuthToFundCallbacks(window.location.origin);
411
- const theme = authSettings?.theme || "auto";
412
-
413
- return (
414
- <div
415
- style={{
416
- ...appWrapperStyle,
417
- maxHeight: CSS.supports("height: 100dvh") ? "100dvh" : `${height}px`,
418
- height: CSS.supports("height: 100dvh") ? "100dvh" : `${height}px`,
419
- }}
420
- onClick={() => closeModal(appIdentifier)}
421
- >
422
- <div
423
- style={{
424
- ...modalStyle,
425
- ...container,
426
- backgroundColor: "transparent",
427
- zIndex: "9999",
428
- opacity: stylesLoaded ? 1 : 0,
429
- }}
430
- onClick={(e) => e.stopPropagation()}
431
- >
432
- <div style={{ ...iframeWrapperStyle, ...iframeStyle }}>
433
- <Auth jwt={jwt} env={authEnv} theme={theme} {...authCallbacks} />
434
- </div>
435
- </div>
436
- </div>
437
- );
438
- }
439
-
440
- return (
441
- <div
442
- style={{
443
- ...appWrapperStyle,
444
- /**
445
- * If browser does not support 100dvh, use height in pixels that is recalculated when
446
- * the window is resized.
447
- */
448
- maxHeight: CSS.supports("height: 100dvh") ? "100dvh" : `${height}px`,
449
- height: CSS.supports("height: 100dvh") ? "100dvh" : `${height}px`,
450
- }}
451
- onClick={() => closeModal(appIdentifier)}
452
- >
453
- <div
454
- style={{ ...modalStyle, ...container, opacity: stylesLoaded ? 1 : 0 }}
455
- >
456
- <div style={iframeWrapperStyle}>
457
- <iframe
458
- ref={iRef}
459
- title={title}
460
- src={iframeURL.toString()}
461
- allow="camera *; fullscreen *; accelerometer *; gyroscope *; magnetometer *; local-network-access *;"
462
- sandbox="allow-downloads allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation"
463
- style={iframeStyle}
464
- />
465
- </div>
466
- </div>
467
- </div>
468
- );
469
- };
470
-
471
- const mapStateToProps = (
472
- state: AppContainerMappedProps,
473
- ownProps: AppContainerProps,
474
- ) => {
475
- return {
476
- isAppActive: state[ownProps.appIdentifier].isAppActive,
477
- isAppLoaded: state[ownProps.appIdentifier].isAppLoaded,
478
- jwt: state[ownProps.appIdentifier].jwt,
479
- filters: state[ownProps.appIdentifier].filters,
480
- navigate:
481
- ownProps.appIdentifier === AppIdentifier.ONBOARDING
482
- ? state[ownProps.appIdentifier].navigate
483
- : undefined,
484
- useAuth:
485
- ownProps.appIdentifier === AppIdentifier.FUND
486
- ? state[ownProps.appIdentifier].useAuth
487
- : undefined,
488
- authSettings:
489
- ownProps.appIdentifier === AppIdentifier.FUND
490
- ? state[ownProps.appIdentifier].authSettings
491
- : undefined,
492
- };
493
- };
494
-
495
- export default connect(mapStateToProps)(AppContainer);