Url Auth: Support apps (#6896)

This commit is contained in:
zubiden 2026-04-27 14:29:20 +02:00 committed by Alexander Zinchuk
parent d5ec8153db
commit fa6224abb7
7 changed files with 45 additions and 42 deletions

View File

@ -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`). - 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. - 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. - 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. - 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. - **IMPORTANT: Avoid conditional spread operators** - TypeScript doesn't check if spread fields match the target type.
```typescript ```typescript
@ -180,7 +182,7 @@ addActionHandler('loadUser', async (global, actions, { userId }) => {
### 3. Hooks ### 3. Hooks
* **useLastCallback** is your go-to for callbacks, since it won't trigger re-renders and always uses the latest scope. * **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. * Only use **useCallback** when you really need to memoize a render function.
* Prefer **useFlag()** over `useState<boolean>()` for simple boolean toggles. * Prefer **useFlag()** over `useState<boolean>()` for simple boolean toggles. `useState` is preferred when component just calls `setState(someVariable)`.
* Check the `hooks/` folders for additional utilities. * Check the `hooks/` folders for additional utilities.
* Avoid adding new `useEffect` where possible. * Avoid adding new `useEffect` where possible.

View File

@ -168,7 +168,7 @@ export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlA
if (result instanceof GramJs.UrlAuthResultRequest) { if (result instanceof GramJs.UrlAuthResultRequest) {
const { const {
bot, domain, requestWriteAccess, requestPhoneNumber, browser, platform, ip, region, matchCodes, bot, domain, requestWriteAccess, requestPhoneNumber, browser, platform, ip, region, matchCodes,
matchCodesFirst, userIdHint, matchCodesFirst, userIdHint, isApp, verifiedAppName,
} = result; } = result;
const user = buildApiUser(bot); const user = buildApiUser(bot);
if (!user) return undefined; if (!user) return undefined;
@ -178,6 +178,7 @@ export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlA
return { return {
type: 'request', type: 'request',
domain, domain,
isApp,
shouldRequestWriteAccess: requestWriteAccess, shouldRequestWriteAccess: requestWriteAccess,
bot: user, bot: user,
shouldRequestPhoneNumber: requestPhoneNumber, shouldRequestPhoneNumber: requestPhoneNumber,
@ -188,6 +189,7 @@ export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlA
matchCodes, matchCodes,
matchCodesFirst, matchCodesFirst,
userIdHint: userIdHint?.toString(), userIdHint: userIdHint?.toString(),
verifiedAppName,
}; };
} }

View File

@ -389,6 +389,7 @@ export type ApiUrlAuthResultRequest = {
type: 'request'; type: 'request';
bot: ApiUser; bot: ApiUser;
domain: string; domain: string;
isApp?: boolean;
shouldRequestWriteAccess?: boolean; shouldRequestWriteAccess?: boolean;
shouldRequestPhoneNumber?: boolean; shouldRequestPhoneNumber?: boolean;
browser?: string; browser?: string;
@ -398,6 +399,7 @@ export type ApiUrlAuthResultRequest = {
matchCodes?: string[]; matchCodes?: string[];
matchCodesFirst?: boolean; matchCodesFirst?: boolean;
userIdHint?: string; userIdHint?: string;
verifiedAppName?: string;
}; };
type ApiUrlAuthResultAccepted = { type ApiUrlAuthResultAccepted = {

View File

@ -613,6 +613,7 @@
"OpenUrlText" = "Do you want to open **{url}**?"; "OpenUrlText" = "Do you want to open **{url}**?";
"OpenUrlConfirm" = "Open"; "OpenUrlConfirm" = "Open";
"BotAuthTitle" = "Log in to {url}"; "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**."; "BotAuthSiteSubtitle" = "This site will receive your **name**, **username** and **profile photo**.";
"BotAuthAllowMessages" = "Allow Messages"; "BotAuthAllowMessages" = "Allow Messages";
"BotAuthAllowMessagesInfo" = "This will allow **{bot}** to message you."; "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}."; "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}."; "ActionBotAllowedFromApp" = "You allowed this bot to message you when you opened {app}.";
"ActionBotAppPlaceholder" = "App"; "ActionBotAppPlaceholder" = "App";
"BotAuthUnverifiedApp" = "Unverified App";
"ActionGiftTextUnknown" = "You've received a gift"; "ActionGiftTextUnknown" = "You've received a gift";
"ActionGiftTextUnknownYou" = "You sent a gift"; "ActionGiftTextUnknownYou" = "You sent a gift";
"ActionGiftTextCost" = "{from} sent you a gift for {cost}"; "ActionGiftTextCost" = "{from} sent you a gift for {cost}";

View File

@ -163,13 +163,19 @@ const UrlAuthModal = ({
|| renderingRequest.browser || renderingRequest.ip || renderingRequest.region, || renderingRequest.browser || renderingRequest.ip || renderingRequest.region,
); );
const requestDomain = renderingRequest.domain; const requestDomain = renderingRequest.domain;
const requestDisplayName = renderingRequest.isApp
? renderingRequest.verifiedAppName || lang('BotAuthUnverifiedApp')
: requestDomain;
const titleTarget = renderingRequest.isApp
? requestDisplayName
: <SafeLink url={requestDomain} text={requestDomain} />;
const formattedPhoneNumber = currentUser?.phoneNumber ? `+${formatPhoneNumber(currentUser.phoneNumber)}` : undefined; const formattedPhoneNumber = currentUser?.phoneNumber ? `+${formatPhoneNumber(currentUser.phoneNumber)}` : undefined;
const titleText = lang('BotAuthTitle', { const titleText = lang('BotAuthTitle', {
url: <SafeLink url={requestDomain} text={requestDomain} />, url: titleTarget,
}, { }, {
withNodes: true, withNodes: true,
}); });
const descriptionText = lang('BotAuthSiteSubtitle', undefined, { const descriptionText = lang(renderingRequest.isApp ? 'BotAuthAppSubtitle' : 'BotAuthSiteSubtitle', undefined, {
withNodes: true, withNodes: true,
withMarkdown: true, withMarkdown: true,
}); });
@ -179,7 +185,7 @@ const UrlAuthModal = ({
<> <>
<p> <p>
{lang('BotAuthPhoneNumberText', { {lang('BotAuthPhoneNumberText', {
domain: requestDomain, domain: requestDisplayName,
phone: formattedPhoneNumber || lang('Phone'), phone: formattedPhoneNumber || lang('Phone'),
}, { }, {
withNodes: true, withNodes: true,
@ -308,7 +314,7 @@ const UrlAuthModal = ({
</div> </div>
<div className={styles.footnote}> <div className={styles.footnote}>
{lang('BotAuthTitle', { {lang('BotAuthTitle', {
url: <SafeLink url={requestDomain} text={requestDomain} />, url: titleTarget,
}, { }, {
withNodes: true, withNodes: true,
})} })}

View File

@ -18,7 +18,7 @@ import { copyTextToClipboard } from '../../../util/clipboard';
import { getUsernameFromDeepLink } from '../../../util/deepLinkParser'; import { getUsernameFromDeepLink } from '../../../util/deepLinkParser';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { pick } from '../../../util/iteratees.ts'; 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 { formatStarsAsText } from '../../../util/localization/format';
import { oldTranslate } from '../../../util/oldLangProvider'; import { oldTranslate } from '../../../util/oldLangProvider';
import requestActionTimeout from '../../../util/requestActionTimeout'; import requestActionTimeout from '../../../util/requestActionTimeout';
@ -1298,41 +1298,28 @@ function handleUrlAuthResult<T extends GlobalState>(
} }
if (result.type === 'accepted' && !result.url) { if (result.type === 'accepted' && !result.url) {
if (!wasPhoneShared && tabState.urlAuth?.request?.shouldRequestPhoneNumber) { const request = tabState.urlAuth?.request;
actions.showNotification({ const requestDisplayName = request?.isApp
message: { ? (request.verifiedAppName || getTranslationFn()('BotAuthUnverifiedApp'))
key: 'BotAuthSuccessTextNoPhone', : (request?.domain || url);
variables: { const successMessage: AdvancedLangFnParameters = {
url: tabState.urlAuth.request?.domain || result.url, key: !wasPhoneShared && request?.shouldRequestPhoneNumber ? 'BotAuthSuccessTextNoPhone' : 'BotAuthSuccessText',
}, variables: {
options: { url: requestDisplayName,
withMarkdown: true, },
withNodes: true, options: {
}, withMarkdown: true,
}, withNodes: true,
title: { },
key: 'BotAuthSuccessTitle', };
},
tabId, actions.showNotification({
}); message: successMessage,
} else { title: {
actions.showNotification({ key: 'BotAuthSuccessTitle',
message: { },
key: 'BotAuthSuccessText', tabId,
variables: { });
url: tabState.urlAuth?.request?.domain || result.url,
},
options: {
withMarkdown: true,
withNodes: true,
},
},
title: {
key: 'BotAuthSuccessTitle',
},
tabId,
});
}
actions.closeUrlAuthModal({ tabId }); actions.closeUrlAuthModal({ tabId });
return; return;
} }

View File

@ -549,6 +549,7 @@ export interface LangPair {
'AboutPremiumDescription2': undefined; 'AboutPremiumDescription2': undefined;
'OpenUrlTitle': undefined; 'OpenUrlTitle': undefined;
'OpenUrlConfirm': undefined; 'OpenUrlConfirm': undefined;
'BotAuthAppSubtitle': undefined;
'BotAuthSiteSubtitle': undefined; 'BotAuthSiteSubtitle': undefined;
'BotAuthAllowMessages': undefined; 'BotAuthAllowMessages': undefined;
'BotAuthInfo': undefined; 'BotAuthInfo': undefined;
@ -1589,6 +1590,7 @@ export interface LangPair {
'NoForwardsRequestSaving': undefined; 'NoForwardsRequestSaving': undefined;
'NoForwardsRequestCopying': undefined; 'NoForwardsRequestCopying': undefined;
'ActionBotAppPlaceholder': undefined; 'ActionBotAppPlaceholder': undefined;
'BotAuthUnverifiedApp': undefined;
'ActionGiftTextUnknown': undefined; 'ActionGiftTextUnknown': undefined;
'ActionGiftTextUnknownYou': undefined; 'ActionGiftTextUnknownYou': undefined;
'ActionGiftUniqueSent': undefined; 'ActionGiftUniqueSent': undefined;