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,430 +0,0 @@
1
- import { renderHook, act } from "@testing-library/react";
2
- import { useStyleUpdates, type StyleConfig } from "../use-style-updates";
3
-
4
- // Jest mock for setTimeout
5
- jest.useFakeTimers();
6
-
7
- describe("useStyleUpdates", () => {
8
- const defaultStyles: StyleConfig = {
9
- appWrapperStyle: { position: "fixed", zIndex: 999999 },
10
- modalStyle: { padding: 0, backgroundColor: "#FFF" },
11
- iframeWrapperStyle: { overflow: "hidden" },
12
- iframeStyle: { width: "100%", height: "100%" },
13
- };
14
-
15
- const zeroHashAppURL = "https://app.example.com/";
16
-
17
- beforeEach(() => {
18
- // Reset console.log mock and window.location.origin
19
- jest.clearAllMocks();
20
- jest.clearAllTimers();
21
- Object.defineProperty(window, "location", {
22
- value: {
23
- origin: "http://localhost",
24
- },
25
- writable: true,
26
- });
27
- });
28
-
29
- afterEach(() => {
30
- jest.clearAllTimers();
31
- });
32
-
33
- describe("initial state", () => {
34
- it("should initialize with default styles", () => {
35
- const { result } = renderHook(() =>
36
- useStyleUpdates(zeroHashAppURL, defaultStyles)
37
- );
38
-
39
- expect(result.current.styles).toEqual(defaultStyles);
40
- expect(result.current.stylesLoaded).toBe(false);
41
- });
42
-
43
- it("should have handleStyleConfig function", () => {
44
- const { result } = renderHook(() =>
45
- useStyleUpdates(zeroHashAppURL, defaultStyles)
46
- );
47
-
48
- expect(typeof result.current.handleStyleConfig).toBe("function");
49
- });
50
- });
51
-
52
- describe("handleStyleConfig with valid origins", () => {
53
- it("should accept styles from matching zeroHashAppURL origin", () => {
54
- const { result } = renderHook(() =>
55
- useStyleUpdates(zeroHashAppURL, defaultStyles)
56
- );
57
-
58
- const incomingStyles: Partial<StyleConfig> = {
59
- iframeStyle: { borderRadius: "8px" },
60
- };
61
-
62
- let success = false;
63
- act(() => {
64
- success = result.current.handleStyleConfig(
65
- incomingStyles,
66
- "https://app.example.com"
67
- );
68
- });
69
-
70
- expect(success).toBe(true);
71
- expect(result.current.stylesLoaded).toBe(true);
72
- expect(result.current.styles.iframeStyle.borderRadius).toBe("8px");
73
- expect(result.current.styles.iframeStyle.width).toBe("100%");
74
- });
75
-
76
- it("should accept styles from window.location.origin", () => {
77
- const { result } = renderHook(() =>
78
- useStyleUpdates(zeroHashAppURL, defaultStyles)
79
- );
80
-
81
- const incomingStyles: Partial<StyleConfig> = {
82
- modalStyle: { padding: "20px" },
83
- };
84
-
85
- let success = false;
86
- act(() => {
87
- success = result.current.handleStyleConfig(
88
- incomingStyles,
89
- "http://localhost"
90
- );
91
- });
92
-
93
- expect(success).toBe(true);
94
- expect(result.current.stylesLoaded).toBe(true);
95
- expect(result.current.styles.modalStyle.padding).toBe("20px");
96
- expect(result.current.styles.modalStyle.backgroundColor).toBe("#FFF");
97
- });
98
-
99
- it("should merge partial styles with defaults", () => {
100
- const { result } = renderHook(() =>
101
- useStyleUpdates(zeroHashAppURL, defaultStyles)
102
- );
103
-
104
- const incomingStyles: Partial<StyleConfig> = {
105
- appWrapperStyle: { opacity: 0.9 },
106
- iframeStyle: { border: "1px solid red" },
107
- };
108
-
109
- act(() => {
110
- result.current.handleStyleConfig(
111
- incomingStyles,
112
- "https://app.example.com"
113
- );
114
- });
115
-
116
- expect(result.current.styles.appWrapperStyle).toEqual({
117
- position: "fixed",
118
- zIndex: 999999,
119
- opacity: 0.9,
120
- });
121
- expect(result.current.styles.iframeStyle).toEqual({
122
- width: "100%",
123
- height: "100%",
124
- border: "1px solid red",
125
- });
126
- expect(result.current.styles.modalStyle).toEqual(defaultStyles.modalStyle);
127
- expect(result.current.styles.iframeWrapperStyle).toEqual(
128
- defaultStyles.iframeWrapperStyle
129
- );
130
- });
131
-
132
- it("should override existing styles when called multiple times", () => {
133
- const { result } = renderHook(() =>
134
- useStyleUpdates(zeroHashAppURL, defaultStyles)
135
- );
136
-
137
- // First update
138
- act(() => {
139
- result.current.handleStyleConfig(
140
- { iframeStyle: { borderRadius: "8px" } },
141
- "https://app.example.com"
142
- );
143
- });
144
-
145
- expect(result.current.styles.iframeStyle.borderRadius).toBe("8px");
146
-
147
- // Second update - should merge, not replace
148
- act(() => {
149
- result.current.handleStyleConfig(
150
- { iframeStyle: { border: "1px solid blue" } },
151
- "https://app.example.com"
152
- );
153
- });
154
-
155
- expect(result.current.styles.iframeStyle.borderRadius).toBe("8px");
156
- expect(result.current.styles.iframeStyle.border).toBe("1px solid blue");
157
- expect(result.current.styles.iframeStyle.width).toBe("100%");
158
- });
159
- });
160
-
161
- describe("handleStyleConfig with invalid origins", () => {
162
- it("should reject styles from mismatched origin", () => {
163
- const { result } = renderHook(() =>
164
- useStyleUpdates(zeroHashAppURL, defaultStyles)
165
- );
166
-
167
- const incomingStyles: Partial<StyleConfig> = {
168
- iframeStyle: { borderRadius: "16px" },
169
- };
170
-
171
- let success = false;
172
- act(() => {
173
- success = result.current.handleStyleConfig(
174
- incomingStyles,
175
- "https://untrusted.com"
176
- );
177
- });
178
-
179
- expect(success).toBe(false);
180
- expect(result.current.stylesLoaded).toBe(false);
181
- expect(result.current.styles).toEqual(defaultStyles);
182
- });
183
-
184
- it("should not modify state when origin validation fails", () => {
185
- const { result } = renderHook(() =>
186
- useStyleUpdates(zeroHashAppURL, defaultStyles)
187
- );
188
-
189
- act(() => {
190
- result.current.handleStyleConfig(
191
- { modalStyle: { backgroundColor: "#000" } },
192
- "https://malicious.com"
193
- );
194
- });
195
-
196
- expect(result.current.styles.modalStyle.backgroundColor).toBe("#FFF");
197
- expect(result.current.stylesLoaded).toBe(false);
198
- });
199
- });
200
-
201
- describe("style merging behavior", () => {
202
- it("should preserve all style properties when merging partial updates", () => {
203
- const { result } = renderHook(() =>
204
- useStyleUpdates(zeroHashAppURL, defaultStyles)
205
- );
206
-
207
- const complexStyles: Partial<StyleConfig> = {
208
- modalStyle: {
209
- padding: "20px",
210
- backgroundColor: "#000",
211
- height: "calc(100% - 50px)",
212
- maxWidth: "calc(100% - 60px)",
213
- },
214
- appWrapperStyle: { zIndex: 9999 },
215
- iframeWrapperStyle: { overflow: "visible" },
216
- iframeStyle: { borderRadius: "8px" },
217
- };
218
-
219
- act(() => {
220
- result.current.handleStyleConfig(
221
- complexStyles,
222
- "https://app.example.com"
223
- );
224
- });
225
-
226
- expect(result.current.styles.modalStyle).toEqual(complexStyles.modalStyle);
227
- expect(result.current.styles.appWrapperStyle).toEqual({
228
- position: "fixed",
229
- zIndex: 9999,
230
- });
231
- expect(result.current.styles.iframeWrapperStyle).toEqual({
232
- overflow: "visible",
233
- });
234
- expect(result.current.styles.iframeStyle).toEqual({
235
- width: "100%",
236
- height: "100%",
237
- borderRadius: "8px",
238
- });
239
- });
240
-
241
- it("should not include undefined styles from incoming updates", () => {
242
- const { result } = renderHook(() =>
243
- useStyleUpdates(zeroHashAppURL, defaultStyles)
244
- );
245
-
246
- const incomingStyles: Partial<StyleConfig> = {
247
- iframeStyle: { width: "50%" },
248
- // modalStyle is intentionally not included
249
- };
250
-
251
- act(() => {
252
- result.current.handleStyleConfig(
253
- incomingStyles,
254
- "https://app.example.com"
255
- );
256
- });
257
-
258
- expect(result.current.styles.iframeStyle).toEqual({
259
- width: "50%",
260
- height: "100%",
261
- });
262
- expect(result.current.styles.modalStyle).toEqual(defaultStyles.modalStyle);
263
- });
264
- });
265
-
266
- describe("stylesLoaded flag", () => {
267
- it("should set stylesLoaded to true after successful style config", () => {
268
- const { result } = renderHook(() =>
269
- useStyleUpdates(zeroHashAppURL, defaultStyles)
270
- );
271
-
272
- expect(result.current.stylesLoaded).toBe(false);
273
-
274
- act(() => {
275
- result.current.handleStyleConfig(
276
- { iframeStyle: { borderRadius: "8px" } },
277
- "https://app.example.com"
278
- );
279
- });
280
-
281
- expect(result.current.stylesLoaded).toBe(true);
282
- });
283
-
284
- it("should remain true after multiple successful updates", () => {
285
- const { result } = renderHook(() =>
286
- useStyleUpdates(zeroHashAppURL, defaultStyles)
287
- );
288
-
289
- act(() => {
290
- result.current.handleStyleConfig(
291
- { iframeStyle: { borderRadius: "8px" } },
292
- "https://app.example.com"
293
- );
294
- });
295
-
296
- expect(result.current.stylesLoaded).toBe(true);
297
-
298
- act(() => {
299
- result.current.handleStyleConfig(
300
- { modalStyle: { padding: "10px" } },
301
- "https://app.example.com"
302
- );
303
- });
304
-
305
- expect(result.current.stylesLoaded).toBe(true);
306
- });
307
-
308
- it("should remain false after failed origin validation", () => {
309
- const { result } = renderHook(() =>
310
- useStyleUpdates(zeroHashAppURL, defaultStyles)
311
- );
312
-
313
- act(() => {
314
- result.current.handleStyleConfig(
315
- { iframeStyle: { borderRadius: "8px" } },
316
- "https://untrusted.com"
317
- );
318
- });
319
-
320
- expect(result.current.stylesLoaded).toBe(false);
321
- });
322
- });
323
-
324
- describe("timeout fallback", () => {
325
- it("should set stylesLoaded to true after 4.5 seconds if STYLE_CONFIG is not received", () => {
326
- const { result } = renderHook(() =>
327
- useStyleUpdates(zeroHashAppURL, defaultStyles)
328
- );
329
-
330
- expect(result.current.stylesLoaded).toBe(false);
331
-
332
- act(() => {
333
- jest.advanceTimersByTime(4500);
334
- });
335
-
336
- expect(result.current.stylesLoaded).toBe(true);
337
- });
338
-
339
- it("should not change stylesLoaded if STYLE_CONFIG is received before timeout", () => {
340
- const { result } = renderHook(() =>
341
- useStyleUpdates(zeroHashAppURL, defaultStyles)
342
- );
343
-
344
- expect(result.current.stylesLoaded).toBe(false);
345
-
346
- // Receive STYLE_CONFIG before timeout
347
- act(() => {
348
- result.current.handleStyleConfig(
349
- { iframeStyle: { borderRadius: "8px" } },
350
- "https://app.example.com"
351
- );
352
- });
353
-
354
- expect(result.current.stylesLoaded).toBe(true);
355
-
356
- // Advance timers past the timeout
357
- act(() => {
358
- jest.advanceTimersByTime(5000);
359
- });
360
-
361
- // Should still be true
362
- expect(result.current.stylesLoaded).toBe(true);
363
- });
364
-
365
- it("should preserve default styles until timeout triggers", () => {
366
- const { result } = renderHook(() =>
367
- useStyleUpdates(zeroHashAppURL, defaultStyles)
368
- );
369
-
370
- expect(result.current.styles).toEqual(defaultStyles);
371
-
372
- act(() => {
373
- jest.advanceTimersByTime(4500);
374
- });
375
-
376
- // Styles should remain unchanged (defaults)
377
- expect(result.current.styles).toEqual(defaultStyles);
378
- expect(result.current.stylesLoaded).toBe(true);
379
- });
380
-
381
- it("should clean up timeout on unmount", () => {
382
- const clearTimeoutSpy = jest.spyOn(global, "clearTimeout");
383
-
384
- const { unmount } = renderHook(() =>
385
- useStyleUpdates(zeroHashAppURL, defaultStyles)
386
- );
387
-
388
- unmount();
389
-
390
- expect(clearTimeoutSpy).toHaveBeenCalled();
391
- clearTimeoutSpy.mockRestore();
392
- });
393
- });
394
-
395
- describe("origin validation scenarios", () => {
396
- it("should handle zeroHashAppURL with trailing slash", () => {
397
- const { result } = renderHook(() =>
398
- useStyleUpdates("https://app.example.com/", defaultStyles)
399
- );
400
-
401
- let success = false;
402
- act(() => {
403
- success = result.current.handleStyleConfig(
404
- { iframeStyle: { width: "50%" } },
405
- "https://app.example.com"
406
- );
407
- });
408
-
409
- expect(success).toBe(true);
410
- expect(result.current.styles.iframeStyle.width).toBe("50%");
411
- });
412
-
413
- it("should handle zeroHashAppURL without trailing slash", () => {
414
- const { result } = renderHook(() =>
415
- useStyleUpdates("https://app.example.com", defaultStyles)
416
- );
417
-
418
- let success = false;
419
- act(() => {
420
- success = result.current.handleStyleConfig(
421
- { iframeStyle: { width: "50%" } },
422
- "https://app.example.com"
423
- );
424
- });
425
-
426
- expect(success).toBe(true);
427
- expect(result.current.styles.iframeStyle.width).toBe("50%");
428
- });
429
- });
430
- });
@@ -1,82 +0,0 @@
1
- import { CSSProperties, useCallback, useEffect, useState } from "react";
2
-
3
- export type StyleConfig = {
4
- appWrapperStyle: CSSProperties;
5
- modalStyle: CSSProperties;
6
- iframeWrapperStyle: CSSProperties;
7
- iframeStyle: CSSProperties;
8
- };
9
-
10
- /**
11
- * Hook to manage style state and updates from postMessage events.
12
- *
13
- * Encapsulates:
14
- * - Style state management (current styles and loaded status)
15
- * - Style merging logic via postMessage
16
- * - Origin validation
17
- * - Fallback timeout to ensure modal displays even if STYLE_CONFIG is not received
18
- *
19
- * @param zeroHashAppURL - The app URL used for origin validation
20
- * @param defaultStyles - Default style configuration to use as fallback
21
- * @returns Object containing current styles, loaded status, and handler function
22
- */
23
- export const useStyleUpdates = (
24
- zeroHashAppURL: string,
25
- defaultStyles: StyleConfig,
26
- ) => {
27
- const [styles, setStyles] = useState<StyleConfig>(defaultStyles);
28
- const [stylesLoaded, setStylesLoaded] = useState(false);
29
-
30
- /**
31
- * Fallback timeout to ensure modal is displayed even if STYLE_CONFIG postMessage
32
- * is not received or the app crashes before sending it. After 4.5 seconds,
33
- * if stylesLoaded is still false, we mark it as true to show the modal with default styles.
34
- */
35
- useEffect(() => {
36
- const timeoutId = setTimeout(() => {
37
- setStylesLoaded((prevState) => prevState || true);
38
- }, 4500);
39
-
40
- return () => clearTimeout(timeoutId);
41
- }, []);
42
-
43
- const handleStyleConfig = useCallback(
44
- (incomingStyles: Partial<StyleConfig>, eventOrigin: string): boolean => {
45
- const zhAppsURLOrigin = new URL(zeroHashAppURL).origin;
46
- /**
47
- * Here we have 2 scenarios, one where the postMessage comes from sdk-ui-apps and another one where
48
- * the postMessage comes from Connect (Auth).
49
- * 1. In the first scenario, the eventOrigin will always be the same as zhAppsURLOrigin, which is expected and valid.
50
- * 2. In the second scenario, the eventOrigin will always be the same as window.location.origin, which is also expected
51
- * and valid since Connect (Auth) is served from the same origin as the main app (no iframe).
52
- */
53
- const isValidOrigin =
54
- eventOrigin === zhAppsURLOrigin ||
55
- eventOrigin === window.location.origin;
56
-
57
- if (!isValidOrigin) {
58
- return false;
59
- }
60
-
61
- setStyles((prev) => ({
62
- appWrapperStyle: incomingStyles.appWrapperStyle
63
- ? { ...prev.appWrapperStyle, ...incomingStyles.appWrapperStyle }
64
- : prev.appWrapperStyle,
65
- modalStyle: incomingStyles.modalStyle
66
- ? { ...prev.modalStyle, ...incomingStyles.modalStyle }
67
- : prev.modalStyle,
68
- iframeWrapperStyle: incomingStyles.iframeWrapperStyle
69
- ? { ...prev.iframeWrapperStyle, ...incomingStyles.iframeWrapperStyle }
70
- : prev.iframeWrapperStyle,
71
- iframeStyle: incomingStyles.iframeStyle
72
- ? { ...prev.iframeStyle, ...incomingStyles.iframeStyle }
73
- : prev.iframeStyle,
74
- }));
75
- setStylesLoaded(true);
76
- return true;
77
- },
78
- [zeroHashAppURL],
79
- );
80
-
81
- return { styles, stylesLoaded, handleStyleConfig };
82
- };