Support updated OAuth (#6798)
This commit is contained in:
parent
6770fff857
commit
d101cdf0d3
@ -166,7 +166,10 @@ export function buildJson(json: GramJs.TypeJSONValue): any {
|
|||||||
|
|
||||||
export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlAuthResult | undefined {
|
export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlAuthResult | undefined {
|
||||||
if (result instanceof GramJs.UrlAuthResultRequest) {
|
if (result instanceof GramJs.UrlAuthResultRequest) {
|
||||||
const { bot, domain, requestWriteAccess } = result;
|
const {
|
||||||
|
bot, domain, requestWriteAccess, requestPhoneNumber, browser, platform, ip, region, matchCodes,
|
||||||
|
matchCodesFirst, userIdHint,
|
||||||
|
} = result;
|
||||||
const user = buildApiUser(bot);
|
const user = buildApiUser(bot);
|
||||||
if (!user) return undefined;
|
if (!user) return undefined;
|
||||||
|
|
||||||
@ -177,6 +180,14 @@ export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlA
|
|||||||
domain,
|
domain,
|
||||||
shouldRequestWriteAccess: requestWriteAccess,
|
shouldRequestWriteAccess: requestWriteAccess,
|
||||||
bot: user,
|
bot: user,
|
||||||
|
shouldRequestPhoneNumber: requestPhoneNumber,
|
||||||
|
browser,
|
||||||
|
platform,
|
||||||
|
ip,
|
||||||
|
region,
|
||||||
|
matchCodes,
|
||||||
|
matchCodesFirst,
|
||||||
|
userIdHint: userIdHint?.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
import { RPCError } from '../../../lib/gramjs/errors';
|
||||||
import { generateRandomBigInt } from '../../../lib/gramjs/Helpers';
|
import { generateRandomBigInt } from '../../../lib/gramjs/Helpers';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -8,6 +9,7 @@ import type {
|
|||||||
ApiInputMessageReplyInfo,
|
ApiInputMessageReplyInfo,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiThemeParameters,
|
ApiThemeParameters,
|
||||||
|
ApiUrlAuthResult,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
@ -450,23 +452,11 @@ export async function requestBotUrlAuth({
|
|||||||
buttonId: number;
|
buttonId: number;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.messages.RequestUrlAuth({
|
return invokeUrlAuthRequest(new GramJs.messages.RequestUrlAuth({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
buttonId,
|
buttonId,
|
||||||
msgId: messageId,
|
msgId: messageId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!result) return undefined;
|
|
||||||
|
|
||||||
const authResult = buildApiUrlAuthResult(result);
|
|
||||||
if (authResult?.type === 'request') {
|
|
||||||
sendApiUpdate({
|
|
||||||
'@type': 'updateUser',
|
|
||||||
id: authResult.bot.id,
|
|
||||||
user: authResult.bot,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return authResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function acceptBotUrlAuth({
|
export async function acceptBotUrlAuth({
|
||||||
@ -474,67 +464,71 @@ export async function acceptBotUrlAuth({
|
|||||||
messageId,
|
messageId,
|
||||||
buttonId,
|
buttonId,
|
||||||
isWriteAllowed,
|
isWriteAllowed,
|
||||||
|
wasPhoneShared,
|
||||||
|
matchCode,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
buttonId: number;
|
buttonId: number;
|
||||||
isWriteAllowed?: boolean;
|
isWriteAllowed?: boolean;
|
||||||
|
wasPhoneShared?: boolean;
|
||||||
|
matchCode?: string;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.messages.AcceptUrlAuth({
|
return invokeUrlAuthRequest(new GramJs.messages.AcceptUrlAuth({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
msgId: messageId,
|
msgId: messageId,
|
||||||
buttonId,
|
buttonId,
|
||||||
writeAllowed: isWriteAllowed || undefined,
|
writeAllowed: isWriteAllowed || undefined,
|
||||||
|
sharePhoneNumber: wasPhoneShared || undefined,
|
||||||
|
matchCode: matchCode || undefined,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!result) return undefined;
|
|
||||||
|
|
||||||
const authResult = buildApiUrlAuthResult(result);
|
|
||||||
if (authResult?.type === 'request') {
|
|
||||||
sendApiUpdate({
|
|
||||||
'@type': 'updateUser',
|
|
||||||
id: authResult.bot.id,
|
|
||||||
user: authResult.bot,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return authResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestLinkUrlAuth({ url }: { url: string }) {
|
export async function requestLinkUrlAuth({ url }: { url: string }) {
|
||||||
const result = await invokeRequest(new GramJs.messages.RequestUrlAuth({
|
return invokeUrlAuthRequest(new GramJs.messages.RequestUrlAuth({
|
||||||
url,
|
url,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!result) return undefined;
|
|
||||||
|
|
||||||
const authResult = buildApiUrlAuthResult(result);
|
|
||||||
if (authResult?.type === 'request') {
|
|
||||||
sendApiUpdate({
|
|
||||||
'@type': 'updateUser',
|
|
||||||
id: authResult.bot.id,
|
|
||||||
user: authResult.bot,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return authResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function acceptLinkUrlAuth({ url, isWriteAllowed }: { url: string; isWriteAllowed?: boolean }) {
|
export async function acceptLinkUrlAuth({
|
||||||
const result = await invokeRequest(new GramJs.messages.AcceptUrlAuth({
|
url, isWriteAllowed, wasPhoneShared, matchCode,
|
||||||
|
}: {
|
||||||
|
url: string;
|
||||||
|
isWriteAllowed?: boolean;
|
||||||
|
wasPhoneShared?: boolean;
|
||||||
|
matchCode?: string;
|
||||||
|
}) {
|
||||||
|
return invokeUrlAuthRequest(new GramJs.messages.AcceptUrlAuth({
|
||||||
url,
|
url,
|
||||||
writeAllowed: isWriteAllowed || undefined,
|
writeAllowed: isWriteAllowed || undefined,
|
||||||
|
sharePhoneNumber: wasPhoneShared || undefined,
|
||||||
|
matchCode: matchCode || undefined,
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (!result) return undefined;
|
export async function checkUrlAuthMatchCode({ url, matchCode }: { url: string; matchCode: string }) {
|
||||||
|
try {
|
||||||
const authResult = buildApiUrlAuthResult(result);
|
const result = await invokeRequest(new GramJs.messages.CheckUrlAuthMatchCode({
|
||||||
if (authResult?.type === 'request') {
|
url,
|
||||||
sendApiUpdate({
|
matchCode,
|
||||||
'@type': 'updateUser',
|
}), {
|
||||||
id: authResult.bot.id,
|
shouldThrow: true,
|
||||||
user: authResult.bot,
|
|
||||||
});
|
});
|
||||||
|
if (!result) return { type: 'unmatched' };
|
||||||
|
|
||||||
|
return { type: 'matched' };
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof RPCError && err.errorMessage === 'URL_EXPIRED') {
|
||||||
|
return { type: 'expired' };
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
return authResult;
|
}
|
||||||
|
|
||||||
|
export async function declineUrlAuth({ url }: { url: string }) {
|
||||||
|
return invokeRequest(new GramJs.messages.DeclineUrlAuth({ url }), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchBotCanSendMessage({ bot }: { bot: ApiUser }) {
|
export function fetchBotCanSendMessage({ bot }: { bot: ApiUser }) {
|
||||||
@ -720,3 +714,27 @@ export async function fetchBotsRecommendations({ user }: { user: ApiChat }) {
|
|||||||
count: result instanceof GramJs.users.UsersSlice ? result.count : similarBots.length,
|
count: result instanceof GramJs.users.UsersSlice ? result.count : similarBots.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function invokeUrlAuthRequest(
|
||||||
|
request: GramJs.messages.RequestUrlAuth | GramJs.messages.AcceptUrlAuth,
|
||||||
|
): Promise<ApiUrlAuthResult | undefined> {
|
||||||
|
try {
|
||||||
|
const result = await invokeRequest(request, { shouldThrow: true });
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
const authResult = buildApiUrlAuthResult(result);
|
||||||
|
if (authResult?.type === 'request') {
|
||||||
|
sendApiUpdate({
|
||||||
|
'@type': 'updateUser',
|
||||||
|
id: authResult.bot.id,
|
||||||
|
user: authResult.bot,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return authResult;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof RPCError && err.errorMessage === 'URL_EXPIRED') {
|
||||||
|
return { type: 'expired' };
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1106,13 +1106,19 @@ export type ApiSearchPostsFlood = {
|
|||||||
starsAmount: number;
|
starsAmount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LinkContext = {
|
export type LinkContextMessage = {
|
||||||
type: 'message';
|
type: 'message';
|
||||||
threadId?: ThreadId;
|
threadId?: ThreadId;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LinkContextInner = {
|
||||||
|
type: 'inner';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LinkContext = LinkContextMessage | LinkContextInner;
|
||||||
|
|
||||||
export interface ApiTopic {
|
export interface ApiTopic {
|
||||||
id: number;
|
id: number;
|
||||||
isClosed?: boolean;
|
isClosed?: boolean;
|
||||||
|
|||||||
@ -348,11 +348,19 @@ export interface ApiEmojiInteraction {
|
|||||||
timestamps: number[];
|
timestamps: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiUrlAuthResultRequest = {
|
export type ApiUrlAuthResultRequest = {
|
||||||
type: 'request';
|
type: 'request';
|
||||||
bot: ApiUser;
|
bot: ApiUser;
|
||||||
domain: string;
|
domain: string;
|
||||||
shouldRequestWriteAccess?: boolean;
|
shouldRequestWriteAccess?: boolean;
|
||||||
|
shouldRequestPhoneNumber?: boolean;
|
||||||
|
browser?: string;
|
||||||
|
platform?: string;
|
||||||
|
ip?: string;
|
||||||
|
region?: string;
|
||||||
|
matchCodes?: string[];
|
||||||
|
matchCodesFirst?: boolean;
|
||||||
|
userIdHint?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ApiUrlAuthResultAccepted = {
|
type ApiUrlAuthResultAccepted = {
|
||||||
@ -360,11 +368,19 @@ type ApiUrlAuthResultAccepted = {
|
|||||||
url?: string;
|
url?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ApiUrlAuthResultExpired = {
|
||||||
|
type: 'expired';
|
||||||
|
};
|
||||||
|
|
||||||
type ApiUrlAuthResultDefault = {
|
type ApiUrlAuthResultDefault = {
|
||||||
type: 'default';
|
type: 'default';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiUrlAuthResult = ApiUrlAuthResultRequest | ApiUrlAuthResultAccepted | ApiUrlAuthResultDefault;
|
export type ApiUrlAuthResult =
|
||||||
|
ApiUrlAuthResultRequest
|
||||||
|
| ApiUrlAuthResultAccepted
|
||||||
|
| ApiUrlAuthResultExpired
|
||||||
|
| ApiUrlAuthResultDefault;
|
||||||
|
|
||||||
export interface ApiCollectibleInfo {
|
export interface ApiCollectibleInfo {
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|||||||
@ -611,8 +611,19 @@
|
|||||||
"OpenUrlTitle" = "Open Link";
|
"OpenUrlTitle" = "Open Link";
|
||||||
"OpenUrlText" = "Do you want to open **{url}**?";
|
"OpenUrlText" = "Do you want to open **{url}**?";
|
||||||
"OpenUrlConfirm" = "Open";
|
"OpenUrlConfirm" = "Open";
|
||||||
"ConversationOpenBotLinkLogin" = "Log in to **{url}** as {user}";
|
"BotAuthTitle" = "Log in to {url}";
|
||||||
"ConversationOpenBotLinkAllowMessages" = "Allow **{bot}** to send me messages";
|
"BotAuthSiteSubtitle" = "This site will receive your **name**, **username** and **profile photo**.";
|
||||||
|
"BotAuthAllowMessages" = "Allow Messages";
|
||||||
|
"BotAuthAllowMessagesInfo" = "This will allow **{bot}** to message you.";
|
||||||
|
"BotAuthInfo" = "This login attempt came from the device above.";
|
||||||
|
"BotAuthDevice" = "Device";
|
||||||
|
"BotAuthSelectEmoji" = "Tap the emoji shown on your other device";
|
||||||
|
"BotAuthPhoneNumber" = "Phone Number";
|
||||||
|
"BotAuthPhoneNumberText" = "**{domain}** wants to access your phone number **{phone}**.";
|
||||||
|
"BotAuthPhoneNumberQuestion" = "Allow access?";
|
||||||
|
"BotAuthPhoneNumberAccept" = "Allow";
|
||||||
|
"BotAuthPhoneNumberDeny" = "Deny";
|
||||||
|
"BotAuthLogin" = "Log in";
|
||||||
"BotWebViewOpenBot" = "Open Bot";
|
"BotWebViewOpenBot" = "Open Bot";
|
||||||
"BotChatMiniAppOpen" = "Open";
|
"BotChatMiniAppOpen" = "Open";
|
||||||
"WebAppReloadPage" = "Reload Page";
|
"WebAppReloadPage" = "Reload Page";
|
||||||
@ -725,6 +736,7 @@
|
|||||||
"ErrorPasswordChanged" = "Password has been changed, please try again";
|
"ErrorPasswordChanged" = "Password has been changed, please try again";
|
||||||
"ErrorPasswordMissing" = "You must set 2FA password to use this feature";
|
"ErrorPasswordMissing" = "You must set 2FA password to use this feature";
|
||||||
"ErrorPasskeyUnknown" = "This passkey is not assigned to any account";
|
"ErrorPasskeyUnknown" = "This passkey is not assigned to any account";
|
||||||
|
"ErrorUrlExpired" = "This link has expired";
|
||||||
"ErrorUnspecified" = "Error";
|
"ErrorUnspecified" = "Error";
|
||||||
"NoStickers" = "No stickers yet";
|
"NoStickers" = "No stickers yet";
|
||||||
"ClearRecentEmoji" = "Clear recent emoji?";
|
"ClearRecentEmoji" = "Clear recent emoji?";
|
||||||
@ -2749,6 +2761,7 @@
|
|||||||
"ChatListAuctionView" = "View";
|
"ChatListAuctionView" = "View";
|
||||||
"BotAuthSuccessTitle" = "Login Successful";
|
"BotAuthSuccessTitle" = "Login Successful";
|
||||||
"BotAuthSuccessText" = "You've successfully logged into **{url}**";
|
"BotAuthSuccessText" = "You've successfully logged into **{url}**";
|
||||||
|
"BotAuthSuccessTextNoPhone" = "You're now logged in to **{url}**, but you didn't grant access to your phone number.";
|
||||||
"GiftPreviewSelectedTraits" = "Selected traits";
|
"GiftPreviewSelectedTraits" = "Selected traits";
|
||||||
"GiftPreviewCountModels_one" = "This collection features **{count}** unique model";
|
"GiftPreviewCountModels_one" = "This collection features **{count}** unique model";
|
||||||
"GiftPreviewCountModels_other" = "This collection features **{count}** unique models";
|
"GiftPreviewCountModels_other" = "This collection features **{count}** unique models";
|
||||||
|
|||||||
@ -426,7 +426,7 @@ const Main = ({
|
|||||||
|
|
||||||
const parsedInitialLocationHash = parseInitialLocationHash();
|
const parsedInitialLocationHash = parseInitialLocationHash();
|
||||||
if (parsedInitialLocationHash?.tgaddr) {
|
if (parsedInitialLocationHash?.tgaddr) {
|
||||||
processDeepLink(decodeURIComponent(parsedInitialLocationHash.tgaddr));
|
processDeepLink(decodeURIComponent(parsedInitialLocationHash.tgaddr), { type: 'inner' });
|
||||||
}
|
}
|
||||||
}, [isSynced]);
|
}, [isSynced]);
|
||||||
|
|
||||||
@ -434,7 +434,7 @@ const Main = ({
|
|||||||
try {
|
try {
|
||||||
const url = event.payload || '';
|
const url = event.payload || '';
|
||||||
const decodedUrl = decodeURIComponent(url);
|
const decodedUrl = decodeURIComponent(url);
|
||||||
processDeepLink(decodedUrl);
|
processDeepLink(decodedUrl, { type: 'inner' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|||||||
71
src/components/modals/urlAuth/UrlAuthModal.module.scss
Normal file
71
src/components/modals/urlAuth/UrlAuthModal.module.scss
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textCenter {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
min-width: 12rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
background-color: var(--color-background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoLabel, .note {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.allowMessages {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoValue {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.matchCodes {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matchCodeButton {
|
||||||
|
--custom-emoji-size: 2rem;
|
||||||
|
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footnote {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancelButton {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
@ -1,121 +1,366 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
|
||||||
import {
|
import {
|
||||||
memo, useCallback, useEffect, useState,
|
memo, useCallback, useEffect, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiUser } from '../../../api/types';
|
import type { ApiUser } from '../../../api/types';
|
||||||
import type { TabState } from '../../../global/types';
|
import type { GlobalState, TabState } from '../../../global/types';
|
||||||
|
|
||||||
import { getUserFullName } from '../../../global/helpers';
|
import { getUserFullName } from '../../../global/helpers';
|
||||||
import { selectUser } from '../../../global/selectors';
|
import { selectAnimatedEmoji, selectUser } from '../../../global/selectors';
|
||||||
import { ensureProtocol } from '../../../util/browser/url';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { formatPhoneNumber } from '../../../util/phoneNumber';
|
||||||
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import { useShallowSelector } from '../../../hooks/data/useSelector';
|
||||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import Checkbox from '../../ui/Checkbox';
|
import Avatar from '../../common/Avatar';
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import CustomEmoji from '../../common/CustomEmoji';
|
||||||
|
import SafeLink from '../../common/SafeLink';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
import Switcher from '../../ui/Switcher';
|
||||||
|
|
||||||
|
import styles from './UrlAuthModal.module.scss';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
modal?: TabState['urlAuth'];
|
modal?: TabState['urlAuth'];
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
|
bot?: ApiUser;
|
||||||
currentUser?: ApiUser;
|
currentUser?: ApiUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UrlAuthModal: FC<OwnProps & StateProps> = ({
|
type AcceptParams = {
|
||||||
modal, currentUser,
|
wasPhoneShared?: boolean;
|
||||||
}) => {
|
matchCode?: string;
|
||||||
const { closeUrlAuthModal, acceptBotUrlAuth, acceptLinkUrlAuth } = getActions();
|
};
|
||||||
const [isLoginChecked, setLoginChecked] = useState(true);
|
|
||||||
const [isWriteAccessChecked, setWriteAccessChecked] = useState(true);
|
|
||||||
const currentAuth = useCurrentOrPrev(modal, false);
|
|
||||||
const { domain, botId, shouldRequestWriteAccess } = currentAuth?.request || {};
|
|
||||||
const bot = botId ? getGlobal().users.byId[botId] : undefined;
|
|
||||||
|
|
||||||
const lang = useOldLang();
|
type DialogState = 'closed' | 'match-confirm' | 'phone';
|
||||||
|
const MATCH_CODE_EMOJI_SIZE = 2 * REM;
|
||||||
|
|
||||||
const handleOpen = useCallback(() => {
|
const UrlAuthModal = ({
|
||||||
if (modal?.url && isLoginChecked) {
|
modal, bot, currentUser,
|
||||||
const acceptAction = modal.button ? acceptBotUrlAuth : acceptLinkUrlAuth;
|
}: OwnProps & StateProps) => {
|
||||||
acceptAction({
|
const {
|
||||||
isWriteAllowed: isWriteAccessChecked,
|
acceptBotUrlAuth, acceptLinkUrlAuth, checkUrlAuthMatchCode, declineUrlAuth,
|
||||||
});
|
} = getActions();
|
||||||
} else if (currentAuth?.url) {
|
const lang = useLang();
|
||||||
window.open(ensureProtocol(currentAuth.url), '_blank', 'noopener');
|
|
||||||
}
|
|
||||||
closeUrlAuthModal();
|
|
||||||
}, [
|
|
||||||
modal, isLoginChecked, closeUrlAuthModal, acceptBotUrlAuth, acceptLinkUrlAuth, isWriteAccessChecked, currentAuth,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
const renderingModal = useCurrentOrPrev(modal, false);
|
||||||
closeUrlAuthModal();
|
const currentBot = useCurrentOrPrev(bot, false);
|
||||||
}, [closeUrlAuthModal]);
|
const modalRequest = modal?.request;
|
||||||
|
const renderingRequest = renderingModal?.request;
|
||||||
|
const confirmedMatchCode = renderingModal?.matchCode;
|
||||||
|
const botName = getUserFullName(currentBot) || currentBot?.firstName;
|
||||||
|
const isMatchCodePrecheckPending = Boolean(
|
||||||
|
modalRequest?.matchCodesFirst && modalRequest.matchCodes?.length && !modal?.matchCode,
|
||||||
|
);
|
||||||
|
const isOpen = Boolean(modal?.url && modal?.request) && !isMatchCodePrecheckPending;
|
||||||
|
|
||||||
const handleLoginChecked = useCallback((value: boolean) => {
|
const [isWriteAccessChecked, setWriteAccessChecked] = useState(
|
||||||
setLoginChecked(value);
|
() => Boolean(modalRequest?.shouldRequestWriteAccess),
|
||||||
setWriteAccessChecked(value);
|
);
|
||||||
}, [setLoginChecked]);
|
const [selectedMatchCode, setSelectedMatchCode] = useState<string | undefined>();
|
||||||
|
const [dialogState, setDialogState] = useState<DialogState>('closed');
|
||||||
|
const effectiveMatchCode = confirmedMatchCode || selectedMatchCode;
|
||||||
|
const isMatchDialogOpen = isMatchCodePrecheckPending || dialogState === 'match-confirm';
|
||||||
|
const isPhoneDialogOpen = dialogState === 'phone';
|
||||||
|
const matchCodeEmojisSelector = useCallback((global: GlobalState) => {
|
||||||
|
return renderingRequest?.matchCodes?.map((matchCode) => selectAnimatedEmoji(global, matchCode));
|
||||||
|
}, [renderingRequest?.matchCodes]);
|
||||||
|
const matchCodeEmojis = useShallowSelector(matchCodeEmojisSelector);
|
||||||
|
|
||||||
// Reset on re-open
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (domain) {
|
if (!modalRequest) {
|
||||||
setLoginChecked(true);
|
setSelectedMatchCode(undefined);
|
||||||
setWriteAccessChecked(Boolean(shouldRequestWriteAccess));
|
setDialogState('closed');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [shouldRequestWriteAccess, domain]);
|
|
||||||
|
setWriteAccessChecked(Boolean(modalRequest.shouldRequestWriteAccess));
|
||||||
|
setSelectedMatchCode(undefined);
|
||||||
|
setDialogState('closed');
|
||||||
|
}, [modalRequest]);
|
||||||
|
|
||||||
|
const handleDismiss = useLastCallback(() => {
|
||||||
|
setDialogState('closed');
|
||||||
|
declineUrlAuth();
|
||||||
|
});
|
||||||
|
|
||||||
|
const submitAuth = useLastCallback((params: AcceptParams = {}) => {
|
||||||
|
const acceptAction = renderingModal?.button ? acceptBotUrlAuth : acceptLinkUrlAuth;
|
||||||
|
acceptAction({
|
||||||
|
isWriteAllowed: renderingRequest?.shouldRequestWriteAccess ? isWriteAccessChecked : undefined,
|
||||||
|
wasPhoneShared: params.wasPhoneShared,
|
||||||
|
matchCode: params.matchCode ?? effectiveMatchCode,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleConfirm = useLastCallback(() => {
|
||||||
|
if (!renderingRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderingRequest.matchCodes?.length && !effectiveMatchCode) {
|
||||||
|
setDialogState('match-confirm');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderingRequest.shouldRequestPhoneNumber) {
|
||||||
|
setDialogState('phone');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitAuth();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMatchDialogClose = useLastCallback(() => {
|
||||||
|
setDialogState('closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMatchCodeSelect = useLastCallback((matchCode: string) => {
|
||||||
|
if (isMatchCodePrecheckPending) {
|
||||||
|
checkUrlAuthMatchCode({ matchCode });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedMatchCode(matchCode);
|
||||||
|
setDialogState('closed');
|
||||||
|
|
||||||
|
if (renderingRequest?.shouldRequestPhoneNumber) {
|
||||||
|
setDialogState('phone');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitAuth({ matchCode });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePhoneDialogClose = useLastCallback(() => {
|
||||||
|
setDialogState('closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePhoneDecision = useLastCallback((wasPhoneShared: boolean) => {
|
||||||
|
setDialogState('closed');
|
||||||
|
submitAuth({ wasPhoneShared });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTriggerWriteAccess = useLastCallback(() => {
|
||||||
|
setWriteAccessChecked(!isWriteAccessChecked);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!renderingRequest) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldRenderSessionInfo = Boolean(renderingRequest.platform
|
||||||
|
|| renderingRequest.browser || renderingRequest.ip || renderingRequest.region,
|
||||||
|
);
|
||||||
|
const requestDomain = renderingRequest.domain;
|
||||||
|
const formattedPhoneNumber = currentUser?.phoneNumber ? `+${formatPhoneNumber(currentUser.phoneNumber)}` : undefined;
|
||||||
|
const titleText = lang('BotAuthTitle', {
|
||||||
|
url: <SafeLink url={requestDomain} text={requestDomain} />,
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
});
|
||||||
|
const descriptionText = lang('BotAuthSiteSubtitle', undefined, {
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderPhoneDialogText() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
{lang('BotAuthPhoneNumberText', {
|
||||||
|
domain: requestDomain,
|
||||||
|
phone: formattedPhoneNumber || lang('Phone'),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p>{lang('BotAuthPhoneNumberQuestion')}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfirmDialog
|
<>
|
||||||
isOpen={Boolean(modal?.url)}
|
<Modal
|
||||||
onClose={handleDismiss}
|
isOpen={isOpen}
|
||||||
title={lang('OpenUrlTitle')}
|
contentClassName={styles.content}
|
||||||
confirmLabel={lang('OpenUrlTitle')}
|
className="tall"
|
||||||
confirmHandler={handleOpen}
|
onClose={handleDismiss}
|
||||||
>
|
hasAbsoluteCloseButton
|
||||||
{renderText(lang('OpenUrlAlert2', currentAuth?.url), ['links'])}
|
isSlim
|
||||||
{domain && (
|
>
|
||||||
<Checkbox
|
<Avatar
|
||||||
className="dialog-checkbox"
|
peer={currentBot}
|
||||||
checked={isLoginChecked}
|
size={96}
|
||||||
label={(
|
className={styles.center}
|
||||||
<>
|
|
||||||
{renderText(
|
|
||||||
lang('Conversation.OpenBotLinkLogin', [domain, getUserFullName(currentUser)]),
|
|
||||||
['simple_markdown'],
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
onCheck={handleLoginChecked}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{shouldRequestWriteAccess && (
|
<h2 className={buildClassName(styles.center, styles.title)} dir="auto">{titleText}</h2>
|
||||||
<Checkbox
|
|
||||||
className="dialog-checkbox"
|
<span className={styles.textCenter}>{descriptionText}</span>
|
||||||
checked={isWriteAccessChecked}
|
|
||||||
label={(
|
{shouldRenderSessionInfo && (
|
||||||
<>
|
<>
|
||||||
{renderText(
|
<div className={styles.infoCard}>
|
||||||
lang('Conversation.OpenBotLinkAllowMessages', getUserFullName(bot)),
|
<span className={styles.infoLabel}>{lang('BotAuthDevice')}</span>
|
||||||
['simple_markdown'],
|
<span className={styles.infoValue}>
|
||||||
|
{[renderingRequest.platform, renderingRequest.browser].filter(Boolean).join(' · ')}
|
||||||
|
</span>
|
||||||
|
<span className={styles.infoLabel}>{lang('SessionPreviewIp')}</span>
|
||||||
|
<span className={styles.infoValue}>{renderingRequest.ip}</span>
|
||||||
|
<span className={styles.infoLabel}>{lang('SessionPreviewLocation')}</span>
|
||||||
|
<span className={styles.infoValue}>{renderingRequest.region}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className={styles.note}>{lang('BotAuthInfo')}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{renderingRequest.shouldRequestWriteAccess && (
|
||||||
|
<>
|
||||||
|
<ListItem
|
||||||
|
className={styles.allowMessages}
|
||||||
|
onClick={handleTriggerWriteAccess}
|
||||||
|
rightElement={(
|
||||||
|
<Switcher
|
||||||
|
id="url_auth_allow_messages"
|
||||||
|
label={lang('BotAuthAllowMessages')}
|
||||||
|
checked={isWriteAccessChecked}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
>
|
||||||
)}
|
{lang('BotAuthAllowMessages')}
|
||||||
onCheck={setWriteAccessChecked}
|
</ListItem>
|
||||||
disabled={!isLoginChecked}
|
{botName && (
|
||||||
/>
|
<span className={styles.note}>
|
||||||
)}
|
{lang(
|
||||||
</ConfirmDialog>
|
'BotAuthAllowMessagesInfo',
|
||||||
|
{ bot: botName },
|
||||||
|
{ withNodes: true, withMarkdown: true },
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={buildClassName(styles.actions, styles.center)}>
|
||||||
|
<Button
|
||||||
|
className={styles.actionButton}
|
||||||
|
color="gray"
|
||||||
|
isText
|
||||||
|
fluid
|
||||||
|
noForcedUpperCase
|
||||||
|
onClick={handleDismiss}
|
||||||
|
>
|
||||||
|
{lang('Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={styles.actionButton}
|
||||||
|
color="primary"
|
||||||
|
fluid
|
||||||
|
noForcedUpperCase
|
||||||
|
onClick={handleConfirm}
|
||||||
|
>
|
||||||
|
{lang('BotAuthLogin')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
isOpen={isMatchDialogOpen}
|
||||||
|
title={lang('BotAuthSelectEmoji')}
|
||||||
|
onClose={isMatchCodePrecheckPending ? handleDismiss : handleMatchDialogClose}
|
||||||
|
className={buildClassName('confirm', styles.matchDialog)}
|
||||||
|
>
|
||||||
|
<div className={styles.matchCodes}>
|
||||||
|
{renderingRequest.matchCodes?.map((matchCode, index) => {
|
||||||
|
const animatedMatchCodeEmoji = matchCodeEmojis?.[index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={matchCode}
|
||||||
|
fluid
|
||||||
|
color="adaptive"
|
||||||
|
className={styles.matchCodeButton}
|
||||||
|
onClick={() => handleMatchCodeSelect(matchCode)}
|
||||||
|
>
|
||||||
|
{animatedMatchCodeEmoji ? (
|
||||||
|
<CustomEmoji
|
||||||
|
sticker={animatedMatchCodeEmoji}
|
||||||
|
size={MATCH_CODE_EMOJI_SIZE}
|
||||||
|
/>
|
||||||
|
) : renderText(matchCode)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className={styles.footnote}>
|
||||||
|
{lang('BotAuthTitle', {
|
||||||
|
url: <SafeLink url={requestDomain} text={requestDomain} />,
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
className={styles.cancelButton}
|
||||||
|
isText
|
||||||
|
noForcedUpperCase
|
||||||
|
onClick={handleDismiss}
|
||||||
|
>
|
||||||
|
{lang('Cancel')}
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
isOpen={isPhoneDialogOpen}
|
||||||
|
title={lang('BotAuthPhoneNumber')}
|
||||||
|
onClose={handlePhoneDialogClose}
|
||||||
|
className={buildClassName('confirm', styles.phoneDialog)}
|
||||||
|
>
|
||||||
|
{renderPhoneDialogText()}
|
||||||
|
<div className="dialog-buttons">
|
||||||
|
<Button
|
||||||
|
className="confirm-dialog-button"
|
||||||
|
color="primary"
|
||||||
|
noForcedUpperCase
|
||||||
|
onClick={() => handlePhoneDecision(true)}
|
||||||
|
>
|
||||||
|
{lang('BotAuthPhoneNumberAccept')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="confirm-dialog-button"
|
||||||
|
color="gray"
|
||||||
|
noForcedUpperCase
|
||||||
|
onClick={() => handlePhoneDecision(false)}
|
||||||
|
>
|
||||||
|
{lang('BotAuthPhoneNumberDeny')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): Complete<StateProps> => {
|
(global, { modal }): Complete<StateProps> => {
|
||||||
const currentUser = selectUser(global, global.currentUserId!);
|
const currentUser = selectUser(global, global.currentUserId!);
|
||||||
|
const bot = modal?.request?.botId ? selectUser(global, modal.request.botId) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
bot,
|
||||||
currentUser,
|
currentUser,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import {
|
|||||||
prepareMessageReplyInfo,
|
prepareMessageReplyInfo,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import {
|
import {
|
||||||
addActionHandler, getGlobal, setGlobal,
|
addActionHandler, getActions, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
import {
|
import {
|
||||||
removeBlockedUser,
|
removeBlockedUser,
|
||||||
@ -1119,6 +1119,11 @@ addActionHandler('requestBotUrlAuth', async (global, actions, payload): Promise<
|
|||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
if (result.type !== 'request') {
|
||||||
|
handleUrlAuthResult(global, { url, result }, tabId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
urlAuth: {
|
urlAuth: {
|
||||||
url,
|
url,
|
||||||
@ -1130,11 +1135,16 @@ addActionHandler('requestBotUrlAuth', async (global, actions, payload): Promise<
|
|||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
handleUrlAuthResult(global, actions, url, result, tabId);
|
handleUrlAuthResult(global, { url, result }, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('acceptBotUrlAuth', async (global, actions, payload): Promise<void> => {
|
addActionHandler('acceptBotUrlAuth', async (global, actions, payload): Promise<void> => {
|
||||||
const { isWriteAllowed, tabId = getCurrentTabId() } = payload;
|
const {
|
||||||
|
isWriteAllowed,
|
||||||
|
wasPhoneShared,
|
||||||
|
matchCode: providedMatchCode,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
if (!tabState.urlAuth?.button) return;
|
if (!tabState.urlAuth?.button) return;
|
||||||
const {
|
const {
|
||||||
@ -1152,10 +1162,12 @@ addActionHandler('acceptBotUrlAuth', async (global, actions, payload): Promise<v
|
|||||||
messageId,
|
messageId,
|
||||||
buttonId,
|
buttonId,
|
||||||
isWriteAllowed,
|
isWriteAllowed,
|
||||||
|
wasPhoneShared,
|
||||||
|
matchCode: providedMatchCode || tabState.urlAuth.matchCode,
|
||||||
});
|
});
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
handleUrlAuthResult(global, actions, url, result, tabId);
|
handleUrlAuthResult(global, { url, result, wasPhoneShared }, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('requestLinkUrlAuth', async (global, actions, payload): Promise<void> => {
|
addActionHandler('requestLinkUrlAuth', async (global, actions, payload): Promise<void> => {
|
||||||
@ -1164,25 +1176,80 @@ addActionHandler('requestLinkUrlAuth', async (global, actions, payload): Promise
|
|||||||
const result = await callApi('requestLinkUrlAuth', { url });
|
const result = await callApi('requestLinkUrlAuth', { url });
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
if (result.type !== 'request') {
|
||||||
|
handleUrlAuthResult(global, { url, result }, tabId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
urlAuth: {
|
urlAuth: {
|
||||||
url,
|
url,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
handleUrlAuthResult(global, actions, url, result, tabId);
|
handleUrlAuthResult(global, { url, result }, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('acceptLinkUrlAuth', async (global, actions, payload): Promise<void> => {
|
addActionHandler('acceptLinkUrlAuth', async (global, actions, payload): Promise<void> => {
|
||||||
const { isWriteAllowed, tabId = getCurrentTabId() } = payload;
|
const {
|
||||||
|
isWriteAllowed,
|
||||||
|
wasPhoneShared,
|
||||||
|
matchCode: providedMatchCode,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
if (!tabState.urlAuth?.url) return;
|
if (!tabState.urlAuth?.url) return;
|
||||||
const { url } = tabState.urlAuth;
|
const { url } = tabState.urlAuth;
|
||||||
|
|
||||||
const result = await callApi('acceptLinkUrlAuth', { url, isWriteAllowed });
|
const result = await callApi('acceptLinkUrlAuth', {
|
||||||
|
url,
|
||||||
|
isWriteAllowed,
|
||||||
|
wasPhoneShared,
|
||||||
|
matchCode: providedMatchCode || tabState.urlAuth.matchCode,
|
||||||
|
});
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
handleUrlAuthResult(global, actions, url, result, tabId);
|
handleUrlAuthResult(global, { url, result, wasPhoneShared }, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('checkUrlAuthMatchCode', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { matchCode, tabId = getCurrentTabId() } = payload;
|
||||||
|
const url = selectTabState(global, tabId).urlAuth?.url;
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
const { type } = await callApi('checkUrlAuthMatchCode', { url, matchCode });
|
||||||
|
if (type === 'unmatched') {
|
||||||
|
actions.closeUrlAuthModal({ tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'expired') {
|
||||||
|
actions.closeUrlAuthModal({ tabId });
|
||||||
|
actions.showNotification({ message: { key: 'ErrorUrlExpired' }, tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
if (!tabState.urlAuth) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
urlAuth: {
|
||||||
|
...tabState.urlAuth,
|
||||||
|
matchCode,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('declineUrlAuth', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
const url = selectTabState(global, tabId).urlAuth?.url;
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
actions.closeUrlAuthModal({ tabId });
|
||||||
|
|
||||||
|
await callApi('declineUrlAuth', { url });
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('closeUrlAuthModal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('closeUrlAuthModal', (global, actions, payload): ActionReturnType => {
|
||||||
@ -1194,22 +1261,33 @@ addActionHandler('closeUrlAuthModal', (global, actions, payload): ActionReturnTy
|
|||||||
|
|
||||||
function handleUrlAuthResult<T extends GlobalState>(
|
function handleUrlAuthResult<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
actions: RequiredGlobalActions,
|
{ url, result, wasPhoneShared }: {
|
||||||
url: string, result: ApiUrlAuthResult,
|
url: string;
|
||||||
|
result: ApiUrlAuthResult;
|
||||||
|
wasPhoneShared?: boolean;
|
||||||
|
},
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
) {
|
) {
|
||||||
|
const actions = getActions();
|
||||||
|
if (result.type === 'expired') {
|
||||||
|
actions.closeUrlAuthModal({ tabId });
|
||||||
|
actions.showNotification({ message: { key: 'ErrorUrlExpired' }, tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|
||||||
if (result.type === 'request') {
|
if (result.type === 'request') {
|
||||||
global = getGlobal();
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
if (!tabState.urlAuth) return;
|
if (!tabState.urlAuth) return;
|
||||||
const { domain, bot, shouldRequestWriteAccess } = result;
|
global = getGlobal();
|
||||||
|
const { type, bot, ...request } = result;
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
urlAuth: {
|
urlAuth: {
|
||||||
...tabState.urlAuth,
|
...tabState.urlAuth,
|
||||||
|
matchCode: undefined,
|
||||||
request: {
|
request: {
|
||||||
domain,
|
...request,
|
||||||
botId: bot.id,
|
botId: bot.id,
|
||||||
shouldRequestWriteAccess,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
@ -1218,22 +1296,41 @@ function handleUrlAuthResult<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.type === 'accepted' && !result.url) {
|
if (result.type === 'accepted' && !result.url) {
|
||||||
actions.showNotification({
|
if (!wasPhoneShared && tabState.urlAuth?.request?.shouldRequestPhoneNumber) {
|
||||||
message: {
|
actions.showNotification({
|
||||||
key: 'BotAuthSuccessText',
|
message: {
|
||||||
variables: {
|
key: 'BotAuthSuccessTextNoPhone',
|
||||||
url,
|
variables: {
|
||||||
|
url: tabState.urlAuth.request?.domain || result.url,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
options: {
|
title: {
|
||||||
withMarkdown: true,
|
key: 'BotAuthSuccessTitle',
|
||||||
withNodes: true,
|
|
||||||
},
|
},
|
||||||
},
|
tabId,
|
||||||
title: {
|
});
|
||||||
key: 'BotAuthSuccessTitle',
|
} else {
|
||||||
},
|
actions.showNotification({
|
||||||
tabId,
|
message: {
|
||||||
});
|
key: 'BotAuthSuccessText',
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3770,7 +3770,7 @@ async function openChatWithParams<T extends GlobalState>(
|
|||||||
if (!isTopicProcessed) {
|
if (!isTopicProcessed) {
|
||||||
actions.focusMessage({
|
actions.focusMessage({
|
||||||
chatId: chat.id, threadId, messageId, timestamp, tabId,
|
chatId: chat.id, threadId, messageId, timestamp, tabId,
|
||||||
replyMessageId: linkContext?.messageId,
|
replyMessageId: linkContext?.type === 'message' ? linkContext.messageId : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (!isCurrentChat) {
|
} else if (!isCurrentChat) {
|
||||||
|
|||||||
@ -2308,6 +2308,8 @@ export interface ActionPayloads {
|
|||||||
|
|
||||||
acceptBotUrlAuth: {
|
acceptBotUrlAuth: {
|
||||||
isWriteAllowed?: boolean;
|
isWriteAllowed?: boolean;
|
||||||
|
wasPhoneShared?: boolean;
|
||||||
|
matchCode?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
requestLinkUrlAuth: {
|
requestLinkUrlAuth: {
|
||||||
@ -2316,8 +2318,16 @@ export interface ActionPayloads {
|
|||||||
|
|
||||||
acceptLinkUrlAuth: {
|
acceptLinkUrlAuth: {
|
||||||
isWriteAllowed?: boolean;
|
isWriteAllowed?: boolean;
|
||||||
|
wasPhoneShared?: boolean;
|
||||||
|
matchCode?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
|
checkUrlAuthMatchCode: {
|
||||||
|
matchCode: string;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
|
declineUrlAuth: WithTabId | undefined;
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
loadAuthorizations: undefined;
|
loadAuthorizations: undefined;
|
||||||
terminateAuthorization: {
|
terminateAuthorization: {
|
||||||
|
|||||||
@ -57,6 +57,7 @@ import type {
|
|||||||
ApiTypePrepaidGiveaway,
|
ApiTypePrepaidGiveaway,
|
||||||
ApiTypeStoryView,
|
ApiTypeStoryView,
|
||||||
ApiUniqueStarGiftValueInfo,
|
ApiUniqueStarGiftValueInfo,
|
||||||
|
ApiUrlAuthResultRequest,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
@ -645,11 +646,8 @@ export type TabState = {
|
|||||||
messageId: number;
|
messageId: number;
|
||||||
buttonId: number;
|
buttonId: number;
|
||||||
};
|
};
|
||||||
request?: {
|
matchCode?: string;
|
||||||
domain: string;
|
request?: Omit<ApiUrlAuthResultRequest, 'type' | 'bot'> & { botId: string };
|
||||||
botId: string;
|
|
||||||
shouldRequestWriteAccess?: boolean;
|
|
||||||
};
|
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1795,6 +1795,8 @@ messages.summarizeText#9d4104e2 flags:# peer:InputPeer id:int to_lang:flags.0?st
|
|||||||
messages.editChatCreator#f743b857 peer:InputPeer user_id:InputUser password:InputCheckPasswordSRP = Updates;
|
messages.editChatCreator#f743b857 peer:InputPeer user_id:InputUser password:InputCheckPasswordSRP = Updates;
|
||||||
messages.getFutureChatCreatorAfterLeave#3b7d0ea6 peer:InputPeer = User;
|
messages.getFutureChatCreatorAfterLeave#3b7d0ea6 peer:InputPeer = User;
|
||||||
messages.editChatParticipantRank#a00f32b0 peer:InputPeer participant:InputPeer rank:string = Updates;
|
messages.editChatParticipantRank#a00f32b0 peer:InputPeer participant:InputPeer rank:string = Updates;
|
||||||
|
messages.declineUrlAuth#35436bbc url:string = Bool;
|
||||||
|
messages.checkUrlAuthMatchCode#c9a47b0b url:string match_code:string = Bool;
|
||||||
updates.getState#edd4882a = updates.State;
|
updates.getState#edd4882a = updates.State;
|
||||||
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
||||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||||
|
|||||||
@ -163,6 +163,8 @@
|
|||||||
"messages.getEmojiKeywordsDifference",
|
"messages.getEmojiKeywordsDifference",
|
||||||
"messages.requestUrlAuth",
|
"messages.requestUrlAuth",
|
||||||
"messages.acceptUrlAuth",
|
"messages.acceptUrlAuth",
|
||||||
|
"messages.declineUrlAuth",
|
||||||
|
"messages.checkUrlAuthMatchCode",
|
||||||
"messages.hidePeerSettingsBar",
|
"messages.hidePeerSettingsBar",
|
||||||
"messages.getScheduledHistory",
|
"messages.getScheduledHistory",
|
||||||
"messages.sendScheduledMessages",
|
"messages.sendScheduledMessages",
|
||||||
|
|||||||
@ -112,7 +112,7 @@
|
|||||||
--color-primary-shade: #{color.mix($color-primary, $color-black, 92%)};
|
--color-primary-shade: #{color.mix($color-primary, $color-black, 92%)};
|
||||||
--color-primary-shade-darker: #{color.mix($color-primary, $color-black, 84%)};
|
--color-primary-shade-darker: #{color.mix($color-primary, $color-black, 84%)};
|
||||||
--color-primary-shade-rgb: #{toRGB(color.mix($color-primary, $color-black, 92%))};
|
--color-primary-shade-rgb: #{toRGB(color.mix($color-primary, $color-black, 92%))};
|
||||||
--color-primary-opacity: rgba(var(--color-primary), 0.15);
|
--color-primary-opacity: rgba(var(--color-primary), 0.2);
|
||||||
--color-primary-opacity-hover: rgba(var(--color-primary), 0.25);
|
--color-primary-opacity-hover: rgba(var(--color-primary), 0.25);
|
||||||
--color-primary-tint: rgba(var(--color-primary), 0.1);
|
--color-primary-tint: rgba(var(--color-primary), 0.1);
|
||||||
--color-green: #{$color-green};
|
--color-green: #{$color-green};
|
||||||
@ -121,7 +121,7 @@
|
|||||||
|
|
||||||
--accent-color: var(--color-primary);
|
--accent-color: var(--color-primary);
|
||||||
--accent-background-color: var(--color-primary-tint);
|
--accent-background-color: var(--color-primary-tint);
|
||||||
--accent-background-active-color: var(--color-primary-opacity);
|
--accent-background-active-color: var(--color-primary-opacity-hover);
|
||||||
|
|
||||||
--color-error: #{$color-error};
|
--color-error: #{$color-error};
|
||||||
--color-error-shade: #{color.mix($color-error, $color-black, 92%)};
|
--color-error-shade: #{color.mix($color-error, $color-black, 92%)};
|
||||||
|
|||||||
23
src/types/language.d.ts
vendored
23
src/types/language.d.ts
vendored
@ -548,6 +548,16 @@ export interface LangPair {
|
|||||||
'AboutPremiumDescription2': undefined;
|
'AboutPremiumDescription2': undefined;
|
||||||
'OpenUrlTitle': undefined;
|
'OpenUrlTitle': undefined;
|
||||||
'OpenUrlConfirm': undefined;
|
'OpenUrlConfirm': undefined;
|
||||||
|
'BotAuthSiteSubtitle': undefined;
|
||||||
|
'BotAuthAllowMessages': undefined;
|
||||||
|
'BotAuthInfo': undefined;
|
||||||
|
'BotAuthDevice': undefined;
|
||||||
|
'BotAuthSelectEmoji': undefined;
|
||||||
|
'BotAuthPhoneNumber': undefined;
|
||||||
|
'BotAuthPhoneNumberQuestion': undefined;
|
||||||
|
'BotAuthPhoneNumberAccept': undefined;
|
||||||
|
'BotAuthPhoneNumberDeny': undefined;
|
||||||
|
'BotAuthLogin': undefined;
|
||||||
'BotWebViewOpenBot': undefined;
|
'BotWebViewOpenBot': undefined;
|
||||||
'BotChatMiniAppOpen': undefined;
|
'BotChatMiniAppOpen': undefined;
|
||||||
'WebAppReloadPage': undefined;
|
'WebAppReloadPage': undefined;
|
||||||
@ -642,6 +652,7 @@ export interface LangPair {
|
|||||||
'ErrorPasswordChanged': undefined;
|
'ErrorPasswordChanged': undefined;
|
||||||
'ErrorPasswordMissing': undefined;
|
'ErrorPasswordMissing': undefined;
|
||||||
'ErrorPasskeyUnknown': undefined;
|
'ErrorPasskeyUnknown': undefined;
|
||||||
|
'ErrorUrlExpired': undefined;
|
||||||
'ErrorUnspecified': undefined;
|
'ErrorUnspecified': undefined;
|
||||||
'NoStickers': undefined;
|
'NoStickers': undefined;
|
||||||
'ClearRecentEmoji': undefined;
|
'ClearRecentEmoji': undefined;
|
||||||
@ -2181,13 +2192,16 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'OpenUrlText': {
|
'OpenUrlText': {
|
||||||
'url': V;
|
'url': V;
|
||||||
};
|
};
|
||||||
'ConversationOpenBotLinkLogin': {
|
'BotAuthTitle': {
|
||||||
'url': V;
|
'url': V;
|
||||||
'user': V;
|
|
||||||
};
|
};
|
||||||
'ConversationOpenBotLinkAllowMessages': {
|
'BotAuthAllowMessagesInfo': {
|
||||||
'bot': V;
|
'bot': V;
|
||||||
};
|
};
|
||||||
|
'BotAuthPhoneNumberText': {
|
||||||
|
'domain': V;
|
||||||
|
'phone': V;
|
||||||
|
};
|
||||||
'ForwardForStars': {
|
'ForwardForStars': {
|
||||||
'price': V;
|
'price': V;
|
||||||
};
|
};
|
||||||
@ -3575,6 +3589,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'BotAuthSuccessText': {
|
'BotAuthSuccessText': {
|
||||||
'url': V;
|
'url': V;
|
||||||
};
|
};
|
||||||
|
'BotAuthSuccessTextNoPhone': {
|
||||||
|
'url': V;
|
||||||
|
};
|
||||||
'RankModalMemberText': {
|
'RankModalMemberText': {
|
||||||
'tag': V;
|
'tag': V;
|
||||||
'author': V;
|
'author': V;
|
||||||
|
|||||||
@ -66,12 +66,8 @@ export const IS_EMOJI_SUPPORTED = PLATFORM_ENV && (IS_MAC_OS || IS_IOS) && isLas
|
|||||||
|
|
||||||
export const IS_SERVICE_WORKER_SUPPORTED = 'serviceWorker' in navigator;
|
export const IS_SERVICE_WORKER_SUPPORTED = 'serviceWorker' in navigator;
|
||||||
|
|
||||||
// Remove in mid-late 2025 when Chromium 132 is no longer a problem
|
|
||||||
// https://issues.chromium.org/issues/390581541
|
|
||||||
const chromeVersion = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)?.[2];
|
|
||||||
const hasBrokenServiceWorkerStreaming = chromeVersion && Number(chromeVersion) === 132;
|
|
||||||
// TODO Consider failed service worker
|
// TODO Consider failed service worker
|
||||||
export const IS_PROGRESSIVE_SUPPORTED = IS_SERVICE_WORKER_SUPPORTED && !hasBrokenServiceWorkerStreaming;
|
export const IS_PROGRESSIVE_SUPPORTED = IS_SERVICE_WORKER_SUPPORTED;
|
||||||
export const IS_OPUS_SUPPORTED = Boolean((new Audio()).canPlayType('audio/ogg; codecs=opus'));
|
export const IS_OPUS_SUPPORTED = Boolean((new Audio()).canPlayType('audio/ogg; codecs=opus'));
|
||||||
export const IS_CANVAS_FILTER_SUPPORTED = (
|
export const IS_CANVAS_FILTER_SUPPORTED = (
|
||||||
!IS_TEST && 'filter' in (document.createElement('canvas').getContext('2d') || {})
|
!IS_TEST && 'filter' in (document.createElement('canvas').getContext('2d') || {})
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { isUsernameValid } from './entities/username';
|
|||||||
export type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
export type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
||||||
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
||||||
'invoice' | 'addlist' | 'boost' | 'giftcode' | 'message' | 'premium_offer' | 'premium_multigift' | 'stars_topup'
|
'invoice' | 'addlist' | 'boost' | 'giftcode' | 'message' | 'premium_offer' | 'premium_multigift' | 'stars_topup'
|
||||||
| 'nft' | 'stars' | 'ton' | 'stargift_auction' | 'premium';
|
| 'nft' | 'stars' | 'ton' | 'stargift_auction' | 'premium' | 'oauth';
|
||||||
|
|
||||||
interface PublicMessageLink {
|
interface PublicMessageLink {
|
||||||
type: 'publicMessageLink';
|
type: 'publicMessageLink';
|
||||||
@ -122,6 +122,11 @@ interface SettingsScreenLink {
|
|||||||
screen?: 'devices' | 'folders' | 'language' | 'privacy' | 'editProfile' | 'theme';
|
screen?: 'devices' | 'folders' | 'language' | 'privacy' | 'editProfile' | 'theme';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OAuthLink {
|
||||||
|
type: 'oauth';
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
type DeepLink =
|
type DeepLink =
|
||||||
TelegramPassportLink |
|
TelegramPassportLink |
|
||||||
LoginCodeLink |
|
LoginCodeLink |
|
||||||
@ -139,7 +144,8 @@ type DeepLink =
|
|||||||
GiftAuctionLink |
|
GiftAuctionLink |
|
||||||
StarsModalLink |
|
StarsModalLink |
|
||||||
TonModalLink |
|
TonModalLink |
|
||||||
SettingsScreenLink;
|
SettingsScreenLink |
|
||||||
|
OAuthLink;
|
||||||
|
|
||||||
type BuilderParams<T extends DeepLink> = Record<keyof Omit<T, 'type'>, string | undefined>;
|
type BuilderParams<T extends DeepLink> = Record<keyof Omit<T, 'type'>, string | undefined>;
|
||||||
type BuilderReturnType<T extends DeepLink> = T | undefined;
|
type BuilderReturnType<T extends DeepLink> = T | undefined;
|
||||||
@ -157,6 +163,10 @@ type PublicUsernameOrBotLinkBuilderParams = Omit<BuilderParams<PublicUsernameOrB
|
|||||||
direct?: string;
|
direct?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OAuthLinkBuilderParams = Omit<BuilderParams<OAuthLink>, 'url'> & {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
const ELIGIBLE_HOSTNAMES = new Set(['t.me', 'telegram.me', 'telegram.dog']);
|
const ELIGIBLE_HOSTNAMES = new Set(['t.me', 'telegram.me', 'telegram.dog']);
|
||||||
|
|
||||||
export function isDeepLink(link: string): boolean {
|
export function isDeepLink(link: string): boolean {
|
||||||
@ -283,6 +293,8 @@ function parseTgLink(url: URL) {
|
|||||||
return { type: 'ton' } satisfies TonModalLink;
|
return { type: 'ton' } satisfies TonModalLink;
|
||||||
case 'settings':
|
case 'settings':
|
||||||
return buildSettingsScreenLink({ screen: pathParams.length === 1 ? pathParams[0] : undefined });
|
return buildSettingsScreenLink({ screen: pathParams.length === 1 ? pathParams[0] : undefined });
|
||||||
|
case 'oauth':
|
||||||
|
return buildOAuthLink({ url: url.toString() });
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -455,12 +467,14 @@ function getTgDeepLinkType(
|
|||||||
switch (method) {
|
switch (method) {
|
||||||
case 'resolve': {
|
case 'resolve': {
|
||||||
const {
|
const {
|
||||||
|
domain, post, bot_id, scope, public_key, nonce, startapp,
|
||||||
domain, post, bot_id, scope, public_key, nonce,
|
|
||||||
} = queryParams;
|
} = queryParams;
|
||||||
if (domain === 'telegrampassport' && bot_id && scope && public_key && nonce) {
|
if (domain === 'telegrampassport' && bot_id && scope && public_key && nonce) {
|
||||||
return 'telegramPassportLink';
|
return 'telegramPassportLink';
|
||||||
}
|
}
|
||||||
|
if (domain === 'oauth' && startapp) {
|
||||||
|
return 'oauth';
|
||||||
|
}
|
||||||
if (domain && post) {
|
if (domain && post) {
|
||||||
return 'publicMessageLink';
|
return 'publicMessageLink';
|
||||||
}
|
}
|
||||||
@ -505,6 +519,8 @@ function getTgDeepLinkType(
|
|||||||
case 'settings': {
|
case 'settings': {
|
||||||
return 'settings';
|
return 'settings';
|
||||||
}
|
}
|
||||||
|
case 'oauth':
|
||||||
|
return 'oauth';
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -773,6 +789,15 @@ function buildSettingsScreenLink(params: BuilderParams<SettingsScreenLink>): Bui
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildOAuthLink(params: OAuthLinkBuilderParams): BuilderReturnType<OAuthLink> {
|
||||||
|
const {
|
||||||
|
url,
|
||||||
|
} = params;
|
||||||
|
return {
|
||||||
|
type: 'oauth',
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
}
|
||||||
function buildPremiumReferrerLink(params: BuilderParams<PremiumReferrerLink>): BuilderReturnType<PremiumReferrerLink> {
|
function buildPremiumReferrerLink(params: BuilderParams<PremiumReferrerLink>): BuilderReturnType<PremiumReferrerLink> {
|
||||||
const {
|
const {
|
||||||
ref,
|
ref,
|
||||||
|
|||||||
@ -90,30 +90,34 @@ export const processDeepLink = (url: string, linkContext?: LinkContext): boolean
|
|||||||
switch (parsedLink.screen) {
|
switch (parsedLink.screen) {
|
||||||
case 'editProfile':
|
case 'editProfile':
|
||||||
actions.openSettingsScreen({ screen: SettingsScreens.EditProfile });
|
actions.openSettingsScreen({ screen: SettingsScreens.EditProfile });
|
||||||
break;
|
return true;
|
||||||
case 'language':
|
case 'language':
|
||||||
actions.openSettingsScreen({ screen: SettingsScreens.Language });
|
actions.openSettingsScreen({ screen: SettingsScreens.Language });
|
||||||
break;
|
return true;
|
||||||
case 'devices':
|
case 'devices':
|
||||||
actions.openSettingsScreen({ screen: SettingsScreens.ActiveSessions });
|
actions.openSettingsScreen({ screen: SettingsScreens.ActiveSessions });
|
||||||
break;
|
return true;
|
||||||
case 'privacy':
|
case 'privacy':
|
||||||
actions.openSettingsScreen({ screen: SettingsScreens.Privacy });
|
actions.openSettingsScreen({ screen: SettingsScreens.Privacy });
|
||||||
break;
|
return true;
|
||||||
case 'folders':
|
case 'folders':
|
||||||
actions.openSettingsScreen({ screen: SettingsScreens.Folders });
|
actions.openSettingsScreen({ screen: SettingsScreens.Folders });
|
||||||
break;
|
return true;
|
||||||
case 'theme':
|
case 'theme':
|
||||||
actions.openSettingsScreen({ screen: SettingsScreens.General });
|
actions.openSettingsScreen({ screen: SettingsScreens.General });
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
break;
|
||||||
case 'stars':
|
case 'stars':
|
||||||
actions.openStarsBalanceModal({});
|
actions.openStarsBalanceModal({});
|
||||||
break;
|
return true;
|
||||||
case 'ton':
|
case 'ton':
|
||||||
actions.openStarsBalanceModal({ currency: TON_CURRENCY_CODE });
|
actions.openStarsBalanceModal({ currency: TON_CURRENCY_CODE });
|
||||||
break;
|
return true;
|
||||||
|
case 'oauth':
|
||||||
|
if (linkContext?.type !== 'inner') return false;
|
||||||
|
actions.requestLinkUrlAuth({ url: parsedLink.url });
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user