diff --git a/src/components/modals/webApp/hooks/useWebAppFrame.ts b/src/components/modals/webApp/hooks/useWebAppFrame.ts index e74ad952d..de3c44694 100644 --- a/src/components/modals/webApp/hooks/useWebAppFrame.ts +++ b/src/components/modals/webApp/hooks/useWebAppFrame.ts @@ -5,6 +5,7 @@ import type { WebAppInboundEvent, WebAppOutboundEvent } from '../../../../types/ import { extractCurrentThemeParams } from '../../../../util/themeStyle'; +import useLastCallback from '../../../../hooks/useLastCallback'; import useWindowSize from '../../../../hooks/useWindowSize'; const SCROLLBAR_STYLE = `* { @@ -27,6 +28,8 @@ const SCROLLBAR_STYLE = `* { background-color: transparent; }`; +const RELOAD_TIMEOUT = 500; + const useWebAppFrame = ( ref: React.RefObject, isOpen: boolean, @@ -41,6 +44,8 @@ const useWebAppFrame = ( closeWebApp, } = getActions(); + const isReloadSupported = useRef(false); + const reloadTimeout = useRef>(); const ignoreEventsRef = useRef(false); const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>(); const windowSize = useWindowSize(); @@ -59,19 +64,33 @@ const useWebAppFrame = ( }; }, [onLoad, ref, isOpen]); - const reloadFrame = useCallback((url: string) => { + const sendEvent = useCallback((event: WebAppOutboundEvent) => { + if (!ref.current?.contentWindow) return; + ref.current.contentWindow.postMessage(JSON.stringify(event), '*'); + }, [ref]); + + const forceReloadFrame = useLastCallback((url: string) => { if (!ref.current) return; const frame = ref.current; frame.src = 'about:blank'; frame.addEventListener('load', () => { frame.src = url; }, { once: true }); - }, [ref]); + }); - const sendEvent = useCallback((event: WebAppOutboundEvent) => { - if (!ref.current?.contentWindow) return; - ref.current.contentWindow.postMessage(JSON.stringify(event), '*'); - }, [ref]); + const reloadFrame = useCallback((url: string) => { + if (isReloadSupported.current) { + sendEvent({ + eventType: 'reload_iframe', + }); + reloadTimeout.current = setTimeout(() => { + forceReloadFrame(url); + }, RELOAD_TIMEOUT); + return; + } + + forceReloadFrame(url); + }, [sendEvent]); const sendViewport = useCallback((isNonStable?: boolean) => { if (!ref.current) { @@ -133,6 +152,11 @@ const useWebAppFrame = ( if (eventType === 'iframe_ready') { const scrollbarColor = getComputedStyle(document.body).getPropertyValue('--color-scrollbar'); sendCustomStyle(SCROLLBAR_STYLE.replace(/%SCROLLBAR_COLOR%/g, scrollbarColor)); + isReloadSupported.current = Boolean(eventData.reload_supported); + } + + if (eventType === 'iframe_will_reload') { + clearTimeout(reloadTimeout.current); } if (eventType === 'web_app_data_send') { diff --git a/src/types/webapp.ts b/src/types/webapp.ts index 8f2eb676d..9f063bd1b 100644 --- a/src/types/webapp.ts +++ b/src/types/webapp.ts @@ -9,6 +9,11 @@ export type PopupOptions = { }; export type WebAppInboundEvent = { + eventType: 'iframe_ready'; + eventData: { + reload_supported?: boolean; + }; +} | { eventType: 'web_app_data_send'; eventData: { data: string; @@ -100,8 +105,8 @@ export type WebAppInboundEvent = { }; } | { eventType: 'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand' - | 'web_app_request_phone' | 'web_app_close' | 'iframe_ready' | 'web_app_close_scan_qr_popup' - | 'web_app_request_write_access' | 'web_app_request_phone'; + | 'web_app_request_phone' | 'web_app_close' | 'web_app_close_scan_qr_popup' + | 'web_app_request_write_access' | 'web_app_request_phone' | 'iframe_will_reload'; eventData: null; }; @@ -176,5 +181,6 @@ export type WebAppOutboundEvent = { error: string; }); } | { - eventType: 'main_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed'; + eventType: 'main_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed' + | 'reload_iframe'; };