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`).
- 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<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.
* Avoid adding new `useEffect` where possible.

View File

@ -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,
};
}

View File

@ -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 = {

View File

@ -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}";

View File

@ -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
: <SafeLink url={requestDomain} text={requestDomain} />;
const formattedPhoneNumber = currentUser?.phoneNumber ? `+${formatPhoneNumber(currentUser.phoneNumber)}` : undefined;
const titleText = lang('BotAuthTitle', {
url: <SafeLink url={requestDomain} text={requestDomain} />,
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 = ({
<>
<p>
{lang('BotAuthPhoneNumberText', {
domain: requestDomain,
domain: requestDisplayName,
phone: formattedPhoneNumber || lang('Phone'),
}, {
withNodes: true,
@ -308,7 +314,7 @@ const UrlAuthModal = ({
</div>
<div className={styles.footnote}>
{lang('BotAuthTitle', {
url: <SafeLink url={requestDomain} text={requestDomain} />,
url: titleTarget,
}, {
withNodes: true,
})}

View File

@ -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<T extends GlobalState>(
}
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;
}

View File

@ -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;