diff --git a/src/api/gramjs/apiBuilders/appConfig.ts b/src/api/gramjs/apiBuilders/appConfig.ts index 48f1e87e1..3a0947ff9 100644 --- a/src/api/gramjs/apiBuilders/appConfig.ts +++ b/src/api/gramjs/apiBuilders/appConfig.ts @@ -120,6 +120,7 @@ export interface GramJsAppConfig extends LimitsConfig { verify_age_min?: number; message_typing_draft_ttl?: number; contact_note_length_limit?: number; + whitelisted_bots?: string[]; settings_display_passkeys?: boolean; passkeys_account_passkeys_max?: number; } @@ -245,6 +246,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp verifyAgeCountry: appConfig.verify_age_country, verifyAgeMin: appConfig.verify_age_min, typingDraftTtl: appConfig.message_typing_draft_ttl, + whitelistedBotIds: appConfig.whitelisted_bots, arePasskeysAvailable: appConfig.settings_display_passkeys, passkeysMaxCount: appConfig.passkeys_account_passkeys_max, }; diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index ff6cc5dd4..0d4828e88 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -285,6 +285,7 @@ export interface ApiAppConfig { verifyAgeMin?: number; typingDraftTtl: number; contactNoteLimit?: number; + whitelistedBotIds?: string[]; arePasskeysAvailable: boolean; passkeysMaxCount: number; } diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 88fd54eb7..ef119ff6d 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -2446,3 +2446,6 @@ "BirthdayPrivacySuggestion" = "Choose who can see your birthday in {link}"; "BirthdayPrivacySuggestionLink" = "Settings >"; "SettingsBirthday" = "Birthday"; +"BotReadTextFromClipboardTitle" = "Clipboard Access"; +"BotReadTextFromClipboardDescription" = "{bot} wants to read the contents of your clipboard. Do you want to continue?"; +"BotReadTextFromClipboardConfirm" = "Allow"; diff --git a/src/components/modals/webApp/WebAppModalTabContent.tsx b/src/components/modals/webApp/WebAppModalTabContent.tsx index 6595a668c..2641d6632 100644 --- a/src/components/modals/webApp/WebAppModalTabContent.tsx +++ b/src/components/modals/webApp/WebAppModalTabContent.tsx @@ -14,7 +14,7 @@ import type { } from '../../../types/webapp'; import { TME_LINK_PREFIX } from '../../../config'; -import { convertToApiChatType } from '../../../global/helpers'; +import { convertToApiChatType, getUserFullName } from '../../../global/helpers'; import { getWebAppKey } from '../../../global/helpers/bots'; import { selectBotAppPermissions, @@ -144,16 +144,17 @@ const WebAppModalTabContent: FC = ({ closeWebAppModal, openPreparedInlineMessageModal, } = getActions(); - const [mainButton, setMainButton] = useState(); - const [secondaryButton, setSecondaryButton] = useState(); + const [mainButton, setMainButton] = useState(); + const [secondaryButton, setSecondaryButton] = useState(); const [isLoaded, markLoaded, markUnloaded] = useFlag(false); - const [popupParameters, setPopupParameters] = useState(); + const [popupParameters, setPopupParameters] = useState(); const [isRequestingPhone, setIsRequestingPhone] = useState(false); const [isRequestingWriteAccess, setIsRequestingWriteAccess] = useState(false); - const [requestedFileDownload, setRequestedFileDownload] = useState<{ url: string; fileName: string } | undefined>(); - const [bottomBarColor, setBottomBarColor] = useState(); + const [clipboardRequestId, setClipboardRequestId] = useState(); + const [requestedFileDownload, setRequestedFileDownload] = useState<{ url: string; fileName: string }>(); + const [bottomBarColor, setBottomBarColor] = useState(); const { unlockPopupsAt, handlePopupOpened, handlePopupClosed, } = usePopupLimit(POPUP_SEQUENTIAL_LIMIT, POPUP_RESET_DELAY); @@ -218,7 +219,7 @@ const WebAppModalTabContent: FC = ({ }, [themeParams]); const themeBackgroundColor = themeParams.bg_color; - const [backgroundColorFromEvent, setBackgroundColorFromEvent] = useState(); + const [backgroundColorFromEvent, setBackgroundColorFromEvent] = useState(); const backgroundColorFromSettings = theme === 'light' ? botAppSettings?.backgroundColor : botAppSettings?.backgroundDarkColor; @@ -229,7 +230,7 @@ const WebAppModalTabContent: FC = ({ }, [themeBackgroundColor, backgroundColorFromEvent, backgroundColorFromSettings]); const themeHeaderColor = themeParams.bg_color; - const [headerColorFromEvent, setHeaderColorFromEvent] = useState(); + const [headerColorFromEvent, setHeaderColorFromEvent] = useState(); const headerColorFromSettings = theme === 'light' ? botAppSettings?.headerColor : botAppSettings?.headerDarkColor; @@ -473,6 +474,44 @@ const WebAppModalTabContent: FC = ({ setIsRequestingWriteAccess(!canWrite); } + const handleRejectClipboardText = useLastCallback(() => { + if (!clipboardRequestId) return; + setClipboardRequestId(undefined); + sendEvent({ + eventType: 'clipboard_text_received', + eventData: { + req_id: clipboardRequestId, + // eslint-disable-next-line no-null/no-null + data: null, + }, + }); + }); + + const handleConfirmClipboardText = useLastCallback(() => { + const reqId = clipboardRequestId; + if (!reqId) return; + + setClipboardRequestId(undefined); + window.navigator.clipboard.readText().then((clipboardText) => { + sendEvent({ + eventType: 'clipboard_text_received', + eventData: { + req_id: reqId, + data: clipboardText, + }, + }); + }).catch(() => { + sendEvent({ + eventType: 'clipboard_text_received', + eventData: { + req_id: reqId, + // eslint-disable-next-line no-null/no-null + data: null, + }, + }); + }); + }); + async function handleCheckDownloadFile(fileUrl: string, fileName: string) { const canDownload = await callApi('checkBotDownloadFileParams', { bot: bot!, @@ -531,6 +570,8 @@ const WebAppModalTabContent: FC = ({ setIsRequestingWriteAccess(false); setMainButton(undefined); setSecondaryButton(undefined); + setRequestedFileDownload(undefined); + setClipboardRequestId(undefined); updateCurrentWebApp({ isSettingsButtonVisible: false, shouldConfirmClosing: false, @@ -766,6 +807,10 @@ const WebAppModalTabContent: FC = ({ if (eventType === 'web_app_open_location_settings') { handleOpenChat(); } + + if (eventType === 'web_app_read_text_from_clipboard') { + setClipboardRequestId(eventData.req_id); + } } const mainButtonCurrentColor = useCurrentOrPrev(mainButton?.color, true); @@ -1187,6 +1232,14 @@ const WebAppModalTabContent: FC = ({ confirmHandler={handleRemoveAttachBot} confirmIsDestructive /> + ); }; diff --git a/src/components/modals/webApp/hooks/useWebAppFrame.ts b/src/components/modals/webApp/hooks/useWebAppFrame.ts index 91ccadf82..3f924a117 100644 --- a/src/components/modals/webApp/hooks/useWebAppFrame.ts +++ b/src/components/modals/webApp/hooks/useWebAppFrame.ts @@ -225,18 +225,6 @@ const useWebAppFrame = ( ignoreEventsRef.current = true; } - // Clipboard access temporarily disabled to address security concerns - if (eventType === 'web_app_read_text_from_clipboard') { - sendEvent({ - eventType: 'clipboard_text_received', - eventData: { - req_id: eventData.req_id, - // eslint-disable-next-line no-null/no-null - data: null, - }, - }); - } - if (eventType === 'web_app_open_scan_qr_popup') { showNotification({ message: 'Scanning QR code is not supported in this client yet', diff --git a/src/global/selectors/chats.ts b/src/global/selectors/chats.ts index 2dec98d36..909754543 100644 --- a/src/global/selectors/chats.ts +++ b/src/global/selectors/chats.ts @@ -91,7 +91,7 @@ export function selectChatOnlineCount(global: T, chat: Ap } export function selectIsTrustedBot(global: T, botId: string) { - return global.trustedBotIds.includes(botId); + return global.trustedBotIds.includes(botId) || global.appConfig.whitelistedBotIds?.includes(botId); } export function selectChatType(global: T, chatId: string): ApiChatType | undefined { diff --git a/src/types/language.d.ts b/src/types/language.d.ts index c251b76e9..20a2e7321 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1821,6 +1821,8 @@ export interface LangPair { 'BirthdayRemove': undefined; 'BirthdayPrivacySuggestionLink': undefined; 'SettingsBirthday': undefined; + 'BotReadTextFromClipboardTitle': undefined; + 'BotReadTextFromClipboardConfirm': undefined; } export interface LangPairWithVariables { @@ -3142,6 +3144,9 @@ export interface LangPairWithVariables { 'BirthdayPrivacySuggestion': { 'link': V; }; + 'BotReadTextFromClipboardDescription': { + 'bot': V; + }; } export interface LangPairPlural {