Mini Apps: Support clipboard read event (#6539)
This commit is contained in:
parent
482f9fc070
commit
ef773026c0
@ -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,
|
||||
};
|
||||
|
||||
@ -285,6 +285,7 @@ export interface ApiAppConfig {
|
||||
verifyAgeMin?: number;
|
||||
typingDraftTtl: number;
|
||||
contactNoteLimit?: number;
|
||||
whitelistedBotIds?: string[];
|
||||
arePasskeysAvailable: boolean;
|
||||
passkeysMaxCount: number;
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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<OwnProps & StateProps> = ({
|
||||
closeWebAppModal,
|
||||
openPreparedInlineMessageModal,
|
||||
} = getActions();
|
||||
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
|
||||
const [secondaryButton, setSecondaryButton] = useState<WebAppButton | undefined>();
|
||||
const [mainButton, setMainButton] = useState<WebAppButton>();
|
||||
const [secondaryButton, setSecondaryButton] = useState<WebAppButton>();
|
||||
|
||||
const [isLoaded, markLoaded, markUnloaded] = useFlag(false);
|
||||
|
||||
const [popupParameters, setPopupParameters] = useState<PopupOptions | undefined>();
|
||||
const [popupParameters, setPopupParameters] = useState<PopupOptions>();
|
||||
const [isRequestingPhone, setIsRequestingPhone] = useState(false);
|
||||
const [isRequestingWriteAccess, setIsRequestingWriteAccess] = useState(false);
|
||||
const [requestedFileDownload, setRequestedFileDownload] = useState<{ url: string; fileName: string } | undefined>();
|
||||
const [bottomBarColor, setBottomBarColor] = useState<string | undefined>();
|
||||
const [clipboardRequestId, setClipboardRequestId] = useState<string>();
|
||||
const [requestedFileDownload, setRequestedFileDownload] = useState<{ url: string; fileName: string }>();
|
||||
const [bottomBarColor, setBottomBarColor] = useState<string>();
|
||||
const {
|
||||
unlockPopupsAt, handlePopupOpened, handlePopupClosed,
|
||||
} = usePopupLimit(POPUP_SEQUENTIAL_LIMIT, POPUP_RESET_DELAY);
|
||||
@ -218,7 +219,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
}, [themeParams]);
|
||||
|
||||
const themeBackgroundColor = themeParams.bg_color;
|
||||
const [backgroundColorFromEvent, setBackgroundColorFromEvent] = useState<string | undefined>();
|
||||
const [backgroundColorFromEvent, setBackgroundColorFromEvent] = useState<string>();
|
||||
const backgroundColorFromSettings = theme === 'light' ? botAppSettings?.backgroundColor
|
||||
: botAppSettings?.backgroundDarkColor;
|
||||
|
||||
@ -229,7 +230,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
}, [themeBackgroundColor, backgroundColorFromEvent, backgroundColorFromSettings]);
|
||||
|
||||
const themeHeaderColor = themeParams.bg_color;
|
||||
const [headerColorFromEvent, setHeaderColorFromEvent] = useState<string | undefined>();
|
||||
const [headerColorFromEvent, setHeaderColorFromEvent] = useState<string>();
|
||||
const headerColorFromSettings = theme === 'light' ? botAppSettings?.headerColor
|
||||
: botAppSettings?.headerDarkColor;
|
||||
|
||||
@ -473,6 +474,44 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
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<OwnProps & StateProps> = ({
|
||||
setIsRequestingWriteAccess(false);
|
||||
setMainButton(undefined);
|
||||
setSecondaryButton(undefined);
|
||||
setRequestedFileDownload(undefined);
|
||||
setClipboardRequestId(undefined);
|
||||
updateCurrentWebApp({
|
||||
isSettingsButtonVisible: false,
|
||||
shouldConfirmClosing: false,
|
||||
@ -766,6 +807,10 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
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<OwnProps & StateProps> = ({
|
||||
confirmHandler={handleRemoveAttachBot}
|
||||
confirmIsDestructive
|
||||
/>
|
||||
<ConfirmDialog
|
||||
isOpen={Boolean(clipboardRequestId)}
|
||||
title={lang('BotReadTextFromClipboardTitle')}
|
||||
text={lang('BotReadTextFromClipboardDescription', { bot: getUserFullName(bot) })}
|
||||
confirmLabel={lang('BotReadTextFromClipboardConfirm')}
|
||||
onClose={handleRejectClipboardText}
|
||||
confirmHandler={handleConfirmClipboardText}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -91,7 +91,7 @@ export function selectChatOnlineCount<T extends GlobalState>(global: T, chat: Ap
|
||||
}
|
||||
|
||||
export function selectIsTrustedBot<T extends GlobalState>(global: T, botId: string) {
|
||||
return global.trustedBotIds.includes(botId);
|
||||
return global.trustedBotIds.includes(botId) || global.appConfig.whitelistedBotIds?.includes(botId);
|
||||
}
|
||||
|
||||
export function selectChatType<T extends GlobalState>(global: T, chatId: string): ApiChatType | undefined {
|
||||
|
||||
5
src/types/language.d.ts
vendored
5
src/types/language.d.ts
vendored
@ -1821,6 +1821,8 @@ export interface LangPair {
|
||||
'BirthdayRemove': undefined;
|
||||
'BirthdayPrivacySuggestionLink': undefined;
|
||||
'SettingsBirthday': undefined;
|
||||
'BotReadTextFromClipboardTitle': undefined;
|
||||
'BotReadTextFromClipboardConfirm': undefined;
|
||||
}
|
||||
|
||||
export interface LangPairWithVariables<V = LangVariable> {
|
||||
@ -3142,6 +3144,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'BirthdayPrivacySuggestion': {
|
||||
'link': V;
|
||||
};
|
||||
'BotReadTextFromClipboardDescription': {
|
||||
'bot': V;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user