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;
|
verify_age_min?: number;
|
||||||
message_typing_draft_ttl?: number;
|
message_typing_draft_ttl?: number;
|
||||||
contact_note_length_limit?: number;
|
contact_note_length_limit?: number;
|
||||||
|
whitelisted_bots?: string[];
|
||||||
settings_display_passkeys?: boolean;
|
settings_display_passkeys?: boolean;
|
||||||
passkeys_account_passkeys_max?: number;
|
passkeys_account_passkeys_max?: number;
|
||||||
}
|
}
|
||||||
@ -245,6 +246,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
verifyAgeCountry: appConfig.verify_age_country,
|
verifyAgeCountry: appConfig.verify_age_country,
|
||||||
verifyAgeMin: appConfig.verify_age_min,
|
verifyAgeMin: appConfig.verify_age_min,
|
||||||
typingDraftTtl: appConfig.message_typing_draft_ttl,
|
typingDraftTtl: appConfig.message_typing_draft_ttl,
|
||||||
|
whitelistedBotIds: appConfig.whitelisted_bots,
|
||||||
arePasskeysAvailable: appConfig.settings_display_passkeys,
|
arePasskeysAvailable: appConfig.settings_display_passkeys,
|
||||||
passkeysMaxCount: appConfig.passkeys_account_passkeys_max,
|
passkeysMaxCount: appConfig.passkeys_account_passkeys_max,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -285,6 +285,7 @@ export interface ApiAppConfig {
|
|||||||
verifyAgeMin?: number;
|
verifyAgeMin?: number;
|
||||||
typingDraftTtl: number;
|
typingDraftTtl: number;
|
||||||
contactNoteLimit?: number;
|
contactNoteLimit?: number;
|
||||||
|
whitelistedBotIds?: string[];
|
||||||
arePasskeysAvailable: boolean;
|
arePasskeysAvailable: boolean;
|
||||||
passkeysMaxCount: number;
|
passkeysMaxCount: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2446,3 +2446,6 @@
|
|||||||
"BirthdayPrivacySuggestion" = "Choose who can see your birthday in {link}";
|
"BirthdayPrivacySuggestion" = "Choose who can see your birthday in {link}";
|
||||||
"BirthdayPrivacySuggestionLink" = "Settings >";
|
"BirthdayPrivacySuggestionLink" = "Settings >";
|
||||||
"SettingsBirthday" = "Birthday";
|
"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';
|
} from '../../../types/webapp';
|
||||||
|
|
||||||
import { TME_LINK_PREFIX } from '../../../config';
|
import { TME_LINK_PREFIX } from '../../../config';
|
||||||
import { convertToApiChatType } from '../../../global/helpers';
|
import { convertToApiChatType, getUserFullName } from '../../../global/helpers';
|
||||||
import { getWebAppKey } from '../../../global/helpers/bots';
|
import { getWebAppKey } from '../../../global/helpers/bots';
|
||||||
import {
|
import {
|
||||||
selectBotAppPermissions,
|
selectBotAppPermissions,
|
||||||
@ -144,16 +144,17 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
closeWebAppModal,
|
closeWebAppModal,
|
||||||
openPreparedInlineMessageModal,
|
openPreparedInlineMessageModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
|
const [mainButton, setMainButton] = useState<WebAppButton>();
|
||||||
const [secondaryButton, setSecondaryButton] = useState<WebAppButton | undefined>();
|
const [secondaryButton, setSecondaryButton] = useState<WebAppButton>();
|
||||||
|
|
||||||
const [isLoaded, markLoaded, markUnloaded] = useFlag(false);
|
const [isLoaded, markLoaded, markUnloaded] = useFlag(false);
|
||||||
|
|
||||||
const [popupParameters, setPopupParameters] = useState<PopupOptions | undefined>();
|
const [popupParameters, setPopupParameters] = useState<PopupOptions>();
|
||||||
const [isRequestingPhone, setIsRequestingPhone] = useState(false);
|
const [isRequestingPhone, setIsRequestingPhone] = useState(false);
|
||||||
const [isRequestingWriteAccess, setIsRequestingWriteAccess] = useState(false);
|
const [isRequestingWriteAccess, setIsRequestingWriteAccess] = useState(false);
|
||||||
const [requestedFileDownload, setRequestedFileDownload] = useState<{ url: string; fileName: string } | undefined>();
|
const [clipboardRequestId, setClipboardRequestId] = useState<string>();
|
||||||
const [bottomBarColor, setBottomBarColor] = useState<string | undefined>();
|
const [requestedFileDownload, setRequestedFileDownload] = useState<{ url: string; fileName: string }>();
|
||||||
|
const [bottomBarColor, setBottomBarColor] = useState<string>();
|
||||||
const {
|
const {
|
||||||
unlockPopupsAt, handlePopupOpened, handlePopupClosed,
|
unlockPopupsAt, handlePopupOpened, handlePopupClosed,
|
||||||
} = usePopupLimit(POPUP_SEQUENTIAL_LIMIT, POPUP_RESET_DELAY);
|
} = usePopupLimit(POPUP_SEQUENTIAL_LIMIT, POPUP_RESET_DELAY);
|
||||||
@ -218,7 +219,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
}, [themeParams]);
|
}, [themeParams]);
|
||||||
|
|
||||||
const themeBackgroundColor = themeParams.bg_color;
|
const themeBackgroundColor = themeParams.bg_color;
|
||||||
const [backgroundColorFromEvent, setBackgroundColorFromEvent] = useState<string | undefined>();
|
const [backgroundColorFromEvent, setBackgroundColorFromEvent] = useState<string>();
|
||||||
const backgroundColorFromSettings = theme === 'light' ? botAppSettings?.backgroundColor
|
const backgroundColorFromSettings = theme === 'light' ? botAppSettings?.backgroundColor
|
||||||
: botAppSettings?.backgroundDarkColor;
|
: botAppSettings?.backgroundDarkColor;
|
||||||
|
|
||||||
@ -229,7 +230,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
}, [themeBackgroundColor, backgroundColorFromEvent, backgroundColorFromSettings]);
|
}, [themeBackgroundColor, backgroundColorFromEvent, backgroundColorFromSettings]);
|
||||||
|
|
||||||
const themeHeaderColor = themeParams.bg_color;
|
const themeHeaderColor = themeParams.bg_color;
|
||||||
const [headerColorFromEvent, setHeaderColorFromEvent] = useState<string | undefined>();
|
const [headerColorFromEvent, setHeaderColorFromEvent] = useState<string>();
|
||||||
const headerColorFromSettings = theme === 'light' ? botAppSettings?.headerColor
|
const headerColorFromSettings = theme === 'light' ? botAppSettings?.headerColor
|
||||||
: botAppSettings?.headerDarkColor;
|
: botAppSettings?.headerDarkColor;
|
||||||
|
|
||||||
@ -473,6 +474,44 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
setIsRequestingWriteAccess(!canWrite);
|
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) {
|
async function handleCheckDownloadFile(fileUrl: string, fileName: string) {
|
||||||
const canDownload = await callApi('checkBotDownloadFileParams', {
|
const canDownload = await callApi('checkBotDownloadFileParams', {
|
||||||
bot: bot!,
|
bot: bot!,
|
||||||
@ -531,6 +570,8 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
setIsRequestingWriteAccess(false);
|
setIsRequestingWriteAccess(false);
|
||||||
setMainButton(undefined);
|
setMainButton(undefined);
|
||||||
setSecondaryButton(undefined);
|
setSecondaryButton(undefined);
|
||||||
|
setRequestedFileDownload(undefined);
|
||||||
|
setClipboardRequestId(undefined);
|
||||||
updateCurrentWebApp({
|
updateCurrentWebApp({
|
||||||
isSettingsButtonVisible: false,
|
isSettingsButtonVisible: false,
|
||||||
shouldConfirmClosing: false,
|
shouldConfirmClosing: false,
|
||||||
@ -766,6 +807,10 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
if (eventType === 'web_app_open_location_settings') {
|
if (eventType === 'web_app_open_location_settings') {
|
||||||
handleOpenChat();
|
handleOpenChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_read_text_from_clipboard') {
|
||||||
|
setClipboardRequestId(eventData.req_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainButtonCurrentColor = useCurrentOrPrev(mainButton?.color, true);
|
const mainButtonCurrentColor = useCurrentOrPrev(mainButton?.color, true);
|
||||||
@ -1187,6 +1232,14 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
confirmHandler={handleRemoveAttachBot}
|
confirmHandler={handleRemoveAttachBot}
|
||||||
confirmIsDestructive
|
confirmIsDestructive
|
||||||
/>
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={Boolean(clipboardRequestId)}
|
||||||
|
title={lang('BotReadTextFromClipboardTitle')}
|
||||||
|
text={lang('BotReadTextFromClipboardDescription', { bot: getUserFullName(bot) })}
|
||||||
|
confirmLabel={lang('BotReadTextFromClipboardConfirm')}
|
||||||
|
onClose={handleRejectClipboardText}
|
||||||
|
confirmHandler={handleConfirmClipboardText}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -225,18 +225,6 @@ const useWebAppFrame = (
|
|||||||
ignoreEventsRef.current = true;
|
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') {
|
if (eventType === 'web_app_open_scan_qr_popup') {
|
||||||
showNotification({
|
showNotification({
|
||||||
message: 'Scanning QR code is not supported in this client yet',
|
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) {
|
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 {
|
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;
|
'BirthdayRemove': undefined;
|
||||||
'BirthdayPrivacySuggestionLink': undefined;
|
'BirthdayPrivacySuggestionLink': undefined;
|
||||||
'SettingsBirthday': undefined;
|
'SettingsBirthday': undefined;
|
||||||
|
'BotReadTextFromClipboardTitle': undefined;
|
||||||
|
'BotReadTextFromClipboardConfirm': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V = LangVariable> {
|
export interface LangPairWithVariables<V = LangVariable> {
|
||||||
@ -3142,6 +3144,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'BirthdayPrivacySuggestion': {
|
'BirthdayPrivacySuggestion': {
|
||||||
'link': V;
|
'link': V;
|
||||||
};
|
};
|
||||||
|
'BotReadTextFromClipboardDescription': {
|
||||||
|
'bot': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairPlural {
|
export interface LangPairPlural {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user