Various fixes for Web App bots (#2102)

This commit is contained in:
Alexander Zinchuk 2022-11-07 23:00:29 +04:00
parent d7ebc691e2
commit 923c946167
4 changed files with 42 additions and 23 deletions

View File

@ -179,7 +179,7 @@ export async function requestWebView({
startParam, startParam,
themeParams: theme ? buildInputThemeParams(theme) : undefined, themeParams: theme ? buildInputThemeParams(theme) : undefined,
fromBotMenu: isFromBotMenu || undefined, fromBotMenu: isFromBotMenu || undefined,
platform: 'web', platform: 'webz',
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
})); }));
@ -204,7 +204,7 @@ export async function requestSimpleWebView({
url, url,
bot: buildInputPeer(bot.id, bot.accessHash), bot: buildInputPeer(bot.id, bot.accessHash),
themeParams: theme ? buildInputThemeParams(theme) : undefined, themeParams: theme ? buildInputThemeParams(theme) : undefined,
platform: 'web', platform: 'webz',
})); }));
return result?.url; return result?.url;

View File

@ -103,6 +103,9 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
const prevPopupParams = usePrevious(popupParams); const prevPopupParams = usePrevious(popupParams);
const renderingPopupParams = popupParams || prevPopupParams; const renderingPopupParams = popupParams || prevPopupParams;
// eslint-disable-next-line no-null/no-null
const frameRef = useRef<HTMLIFrameElement>(null);
const lang = useLang(); const lang = useLang();
const { const {
url, buttonText, queryId, url, buttonText, queryId,
@ -191,8 +194,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
]); ]);
const { const {
ref, reloadFrame, sendEvent, sendViewport, sendTheme, reloadFrame, sendEvent, sendViewport, sendTheme,
} = useWebAppFrame(isOpen, isSimple, handleEvent); } = useWebAppFrame(frameRef, isOpen, isSimple, handleEvent);
const shouldShowMainButton = mainButton?.isVisible && mainButton.text.trim().length > 0; const shouldShowMainButton = mainButton?.isVisible && mainButton.text.trim().length > 0;
@ -238,6 +241,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
}); });
}, [sendEvent]); }, [sendEvent]);
const handlePopupModalClose = useCallback(() => {
handlePopupClose();
}, [handlePopupClose]);
// Notify view that height changed // Notify view that height changed
useOnChange(() => { useOnChange(() => {
setTimeout(() => { setTimeout(() => {
@ -427,13 +434,13 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
{isOpen && ( {isOpen && (
<> <>
<iframe <iframe
ref={ref}
className={buildClassName('web-app-frame', shouldDecreaseWebFrameSize && 'with-button')} className={buildClassName('web-app-frame', shouldDecreaseWebFrameSize && 'with-button')}
src={url} src={url}
title={`${bot?.firstName} Web App`} title={`${bot?.firstName} Web App`}
sandbox={SANDBOX_ATTRIBUTES} sandbox={SANDBOX_ATTRIBUTES}
allow="camera; microphone; geolocation;" allow="camera; microphone; geolocation;"
allowFullScreen allowFullScreen
ref={frameRef}
/> />
<Button <Button
className={buildClassName( className={buildClassName(
@ -465,7 +472,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
<Modal <Modal
isOpen={Boolean(popupParams)} isOpen={Boolean(popupParams)}
title={renderingPopupParams.title || NBSP} title={renderingPopupParams.title || NBSP}
onClose={handlePopupClose} onClose={handlePopupModalClose}
hasCloseButton hasCloseButton
className={buildClassName('web-app-popup', !renderingPopupParams.title?.trim().length && 'without-title')} className={buildClassName('web-app-popup', !renderingPopupParams.title?.trim().length && 'without-title')}
> >

View File

@ -1,6 +1,6 @@
import useWindowSize from '../../../hooks/useWindowSize';
import { useCallback, useEffect, useRef } from '../../../lib/teact/teact'; import { useCallback, useEffect, useRef } from '../../../lib/teact/teact';
import { extractCurrentThemeParams } from '../../../util/themeStyle'; import { extractCurrentThemeParams } from '../../../util/themeStyle';
import useWindowSize from '../../../hooks/useWindowSize';
export type PopupOptions = { export type PopupOptions = {
title: string; title: string;
@ -84,6 +84,7 @@ type WebAppOutboundEvent = {
height: number; height: number;
width?: number; width?: number;
is_expanded?: boolean; is_expanded?: boolean;
is_state_stable?: boolean;
}; };
} | { } | {
eventType: 'theme_changed'; eventType: 'theme_changed';
@ -141,10 +142,14 @@ const SCROLLBAR_STYLE = `* {
background-color: transparent; background-color: transparent;
}`; }`;
const useWebAppFrame = (isOpen: boolean, isSimpleView: boolean, onEvent: (event: WebAppInboundEvent) => void) => { const useWebAppFrame = (
// eslint-disable-next-line no-null/no-null ref: React.RefObject<HTMLIFrameElement>,
const ref = useRef<HTMLIFrameElement>(null); isOpen: boolean,
isSimpleView: boolean,
onEvent: (event: WebAppInboundEvent) => void,
) => {
const ignoreEventsRef = useRef<boolean>(false); const ignoreEventsRef = useRef<boolean>(false);
const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const reloadFrame = useCallback((url: string) => { const reloadFrame = useCallback((url: string) => {
@ -154,14 +159,14 @@ const useWebAppFrame = (isOpen: boolean, isSimpleView: boolean, onEvent: (event:
frame.addEventListener('load', () => { frame.addEventListener('load', () => {
frame.src = url; frame.src = url;
}, { once: true }); }, { once: true });
}, []); }, [ref]);
const sendEvent = useCallback((event: WebAppOutboundEvent) => { const sendEvent = useCallback((event: WebAppOutboundEvent) => {
if (!ref.current?.contentWindow) return; if (!ref.current?.contentWindow) return;
ref.current.contentWindow.postMessage(JSON.stringify(event), '*'); ref.current.contentWindow.postMessage(JSON.stringify(event), '*');
}, []); }, [ref]);
const sendViewport = useCallback(() => { const sendViewport = useCallback((isNonStable?: boolean) => {
if (!ref.current) { if (!ref.current) {
return; return;
} }
@ -172,9 +177,10 @@ const useWebAppFrame = (isOpen: boolean, isSimpleView: boolean, onEvent: (event:
width, width,
height, height,
is_expanded: true, is_expanded: true,
is_state_stable: !isNonStable,
}, },
}); });
}, [sendEvent]); }, [sendEvent, ref]);
const sendTheme = useCallback(() => { const sendTheme = useCallback(() => {
sendEvent({ sendEvent({
@ -201,7 +207,7 @@ const useWebAppFrame = (isOpen: boolean, isSimpleView: boolean, onEvent: (event:
const data = JSON.parse(event.data) as WebAppInboundEvent; const data = JSON.parse(event.data) as WebAppInboundEvent;
// Handle some app requests here to simplify hook usage // Handle some app requests here to simplify hook usage
if (data.eventType === 'web_app_request_viewport') { if (data.eventType === 'web_app_request_viewport') {
sendViewport(); sendViewport(windowSize.isResizing);
} }
if (data.eventType === 'web_app_request_theme') { if (data.eventType === 'web_app_request_theme') {
@ -221,12 +227,14 @@ const useWebAppFrame = (isOpen: boolean, isSimpleView: boolean, onEvent: (event:
} catch (err) { } catch (err) {
// Ignore other messages // Ignore other messages
} }
}, [isSimpleView, onEvent, sendCustomStyle, sendTheme, sendViewport]); }, [isSimpleView, onEvent, sendCustomStyle, sendTheme, sendViewport, windowSize]);
useEffect(() => { useEffect(() => {
if (windowSize) { const { width, height, isResizing } = windowSize;
sendViewport(); if (lastFrameSizeRef.current && lastFrameSizeRef.current.width === width
} && lastFrameSizeRef.current.height === height && !lastFrameSizeRef.current.isResizing) return;
lastFrameSizeRef.current = { width, height, isResizing };
sendViewport(isResizing);
}, [sendViewport, windowSize]); }, [sendViewport, windowSize]);
useEffect(() => { useEffect(() => {
@ -238,11 +246,13 @@ const useWebAppFrame = (isOpen: boolean, isSimpleView: boolean, onEvent: (event:
if (isOpen && ref.current?.contentWindow) { if (isOpen && ref.current?.contentWindow) {
sendViewport(); sendViewport();
ignoreEventsRef.current = false; ignoreEventsRef.current = false;
} else {
lastFrameSizeRef.current = undefined;
} }
}, [isOpen, sendViewport]); }, [isOpen, sendViewport, ref]);
return { return {
ref, sendEvent, reloadFrame, sendViewport, sendTheme, sendEvent, reloadFrame, sendViewport, sendTheme,
}; };
}; };

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from '../lib/teact/teact'; import { useEffect, useMemo, useState } from '../lib/teact/teact';
import type { ApiDimensions } from '../api/types'; import type { ApiDimensions } from '../api/types';
import { throttle } from '../util/schedulers'; import { throttle } from '../util/schedulers';
@ -12,6 +12,8 @@ const useWindowSize = () => {
const [isResizing, setIsResizing] = useState(false); const [isResizing, setIsResizing] = useState(false);
const setIsResizingDebounced = useDebouncedCallback(setIsResizing, [], THROTTLE, true); const setIsResizingDebounced = useDebouncedCallback(setIsResizing, [], THROTTLE, true);
const result = useMemo(() => ({ ...size, isResizing }), [isResizing, size]);
useEffect(() => { useEffect(() => {
const throttledSetIsResizing = throttle(() => { const throttledSetIsResizing = throttle(() => {
setIsResizing(true); setIsResizing(true);
@ -34,7 +36,7 @@ const useWindowSize = () => {
}; };
}, [setIsResizingDebounced]); }, [setIsResizingDebounced]);
return { ...size, isResizing }; return result;
}; };
export default useWindowSize; export default useWindowSize;