From fa6224abb772f8ba788d87ab3035b39056054713 Mon Sep 17 00:00:00 2001 From: zubiden <19638254+zubiden@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:29:20 +0200 Subject: [PATCH] Url Auth: Support apps (#6896) --- CLAUDE.md | 4 +- src/api/gramjs/apiBuilders/misc.ts | 4 +- src/api/types/misc.ts | 2 + src/assets/localization/fallback.strings | 2 + .../modals/urlAuth/UrlAuthModal.tsx | 14 +++-- src/global/actions/api/bots.ts | 59 ++++++++----------- src/types/language.d.ts | 2 + 7 files changed, 45 insertions(+), 42 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ba401963d..610833a66 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,6 +34,8 @@ You are an expert in TypeScript, JavaScript, HTML, SCSS and Teact with deep expe - Functions should start with a verb (e.g. `openModal`, `closeDialog`, `handleClick`). - Prefer checking required parameter before calling a function, avoid making it optional and checking at the beginning of the function. - Only leave comments for complex logic. + - Avoid using default values for props that can be intentionally undefined/false. + - No unnecessary `as` casts. Prefer `satisfies` where possible. - Do not use `null`. There's linter rule to enforce it. - **IMPORTANT: Avoid conditional spread operators** - TypeScript doesn't check if spread fields match the target type. ```typescript @@ -180,7 +182,7 @@ addActionHandler('loadUser', async (global, actions, { userId }) => { ### 3. Hooks * **useLastCallback** is your go-to for callbacks, since it won't trigger re-renders and always uses the latest scope. * Only use **useCallback** when you really need to memoize a render function. -* Prefer **useFlag()** over `useState()` for simple boolean toggles. +* Prefer **useFlag()** over `useState()` for simple boolean toggles. `useState` is preferred when component just calls `setState(someVariable)`. * Check the `hooks/` folders for additional utilities. * Avoid adding new `useEffect` where possible. diff --git a/src/api/gramjs/apiBuilders/misc.ts b/src/api/gramjs/apiBuilders/misc.ts index 7920ea4e4..5eb377898 100644 --- a/src/api/gramjs/apiBuilders/misc.ts +++ b/src/api/gramjs/apiBuilders/misc.ts @@ -168,7 +168,7 @@ export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlA if (result instanceof GramJs.UrlAuthResultRequest) { const { bot, domain, requestWriteAccess, requestPhoneNumber, browser, platform, ip, region, matchCodes, - matchCodesFirst, userIdHint, + matchCodesFirst, userIdHint, isApp, verifiedAppName, } = result; const user = buildApiUser(bot); if (!user) return undefined; @@ -178,6 +178,7 @@ export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlA return { type: 'request', domain, + isApp, shouldRequestWriteAccess: requestWriteAccess, bot: user, shouldRequestPhoneNumber: requestPhoneNumber, @@ -188,6 +189,7 @@ export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlA matchCodes, matchCodesFirst, userIdHint: userIdHint?.toString(), + verifiedAppName, }; } diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 9e87f7222..bdb83a7eb 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -389,6 +389,7 @@ export type ApiUrlAuthResultRequest = { type: 'request'; bot: ApiUser; domain: string; + isApp?: boolean; shouldRequestWriteAccess?: boolean; shouldRequestPhoneNumber?: boolean; browser?: string; @@ -398,6 +399,7 @@ export type ApiUrlAuthResultRequest = { matchCodes?: string[]; matchCodesFirst?: boolean; userIdHint?: string; + verifiedAppName?: string; }; type ApiUrlAuthResultAccepted = { diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 7f709f3fb..465906a1a 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -613,6 +613,7 @@ "OpenUrlText" = "Do you want to open **{url}**?"; "OpenUrlConfirm" = "Open"; "BotAuthTitle" = "Log in to {url}"; +"BotAuthAppSubtitle" = "This app will receive your **name**, **username** and **profile photo**."; "BotAuthSiteSubtitle" = "This site will receive your **name**, **username** and **profile photo**."; "BotAuthAllowMessages" = "Allow Messages"; "BotAuthAllowMessagesInfo" = "This will allow **{bot}** to message you."; @@ -2007,6 +2008,7 @@ "ActionBotAllowedFromDomain" = "You allowed this bot to message you when you logged in on {domain}."; "ActionBotAllowedFromApp" = "You allowed this bot to message you when you opened {app}."; "ActionBotAppPlaceholder" = "App"; +"BotAuthUnverifiedApp" = "Unverified App"; "ActionGiftTextUnknown" = "You've received a gift"; "ActionGiftTextUnknownYou" = "You sent a gift"; "ActionGiftTextCost" = "{from} sent you a gift for {cost}"; diff --git a/src/components/modals/urlAuth/UrlAuthModal.tsx b/src/components/modals/urlAuth/UrlAuthModal.tsx index 512d44f18..e1864489d 100644 --- a/src/components/modals/urlAuth/UrlAuthModal.tsx +++ b/src/components/modals/urlAuth/UrlAuthModal.tsx @@ -163,13 +163,19 @@ const UrlAuthModal = ({ || renderingRequest.browser || renderingRequest.ip || renderingRequest.region, ); const requestDomain = renderingRequest.domain; + const requestDisplayName = renderingRequest.isApp + ? renderingRequest.verifiedAppName || lang('BotAuthUnverifiedApp') + : requestDomain; + const titleTarget = renderingRequest.isApp + ? requestDisplayName + : ; const formattedPhoneNumber = currentUser?.phoneNumber ? `+${formatPhoneNumber(currentUser.phoneNumber)}` : undefined; const titleText = lang('BotAuthTitle', { - url: , + url: titleTarget, }, { withNodes: true, }); - const descriptionText = lang('BotAuthSiteSubtitle', undefined, { + const descriptionText = lang(renderingRequest.isApp ? 'BotAuthAppSubtitle' : 'BotAuthSiteSubtitle', undefined, { withNodes: true, withMarkdown: true, }); @@ -179,7 +185,7 @@ const UrlAuthModal = ({ <>

{lang('BotAuthPhoneNumberText', { - domain: requestDomain, + domain: requestDisplayName, phone: formattedPhoneNumber || lang('Phone'), }, { withNodes: true, @@ -308,7 +314,7 @@ const UrlAuthModal = ({

{lang('BotAuthTitle', { - url: , + url: titleTarget, }, { withNodes: true, })} diff --git a/src/global/actions/api/bots.ts b/src/global/actions/api/bots.ts index 75365cffa..5e4fa6b4b 100644 --- a/src/global/actions/api/bots.ts +++ b/src/global/actions/api/bots.ts @@ -18,7 +18,7 @@ import { copyTextToClipboard } from '../../../util/clipboard'; import { getUsernameFromDeepLink } from '../../../util/deepLinkParser'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { pick } from '../../../util/iteratees.ts'; -import { getTranslationFn } from '../../../util/localization'; +import { type AdvancedLangFnParameters, getTranslationFn } from '../../../util/localization'; import { formatStarsAsText } from '../../../util/localization/format'; import { oldTranslate } from '../../../util/oldLangProvider'; import requestActionTimeout from '../../../util/requestActionTimeout'; @@ -1298,41 +1298,28 @@ function handleUrlAuthResult( } if (result.type === 'accepted' && !result.url) { - if (!wasPhoneShared && tabState.urlAuth?.request?.shouldRequestPhoneNumber) { - actions.showNotification({ - message: { - key: 'BotAuthSuccessTextNoPhone', - variables: { - url: tabState.urlAuth.request?.domain || result.url, - }, - options: { - withMarkdown: true, - withNodes: true, - }, - }, - title: { - key: 'BotAuthSuccessTitle', - }, - tabId, - }); - } else { - actions.showNotification({ - message: { - key: 'BotAuthSuccessText', - variables: { - url: tabState.urlAuth?.request?.domain || result.url, - }, - options: { - withMarkdown: true, - withNodes: true, - }, - }, - title: { - key: 'BotAuthSuccessTitle', - }, - tabId, - }); - } + const request = tabState.urlAuth?.request; + const requestDisplayName = request?.isApp + ? (request.verifiedAppName || getTranslationFn()('BotAuthUnverifiedApp')) + : (request?.domain || url); + const successMessage: AdvancedLangFnParameters = { + key: !wasPhoneShared && request?.shouldRequestPhoneNumber ? 'BotAuthSuccessTextNoPhone' : 'BotAuthSuccessText', + variables: { + url: requestDisplayName, + }, + options: { + withMarkdown: true, + withNodes: true, + }, + }; + + actions.showNotification({ + message: successMessage, + title: { + key: 'BotAuthSuccessTitle', + }, + tabId, + }); actions.closeUrlAuthModal({ tabId }); return; } diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 4922e07c9..31692c43a 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -549,6 +549,7 @@ export interface LangPair { 'AboutPremiumDescription2': undefined; 'OpenUrlTitle': undefined; 'OpenUrlConfirm': undefined; + 'BotAuthAppSubtitle': undefined; 'BotAuthSiteSubtitle': undefined; 'BotAuthAllowMessages': undefined; 'BotAuthInfo': undefined; @@ -1589,6 +1590,7 @@ export interface LangPair { 'NoForwardsRequestSaving': undefined; 'NoForwardsRequestCopying': undefined; 'ActionBotAppPlaceholder': undefined; + 'BotAuthUnverifiedApp': undefined; 'ActionGiftTextUnknown': undefined; 'ActionGiftTextUnknownYou': undefined; 'ActionGiftUniqueSent': undefined;