mirror of
https://github.com/danog/telegram-tt.git
synced 2024-11-30 04:39:00 +01:00
Implement Seamless Login (#1865)
This commit is contained in:
parent
52fa0c8fca
commit
b39ae6f0a1
@ -17,6 +17,9 @@ type GramJsAppConfig = {
|
||||
reactions_uniq_max: number;
|
||||
chat_read_mark_size_threshold: number;
|
||||
chat_read_mark_expire_period: number;
|
||||
autologin_domains: string[];
|
||||
autologin_token: string;
|
||||
url_auth_domains: string[];
|
||||
};
|
||||
|
||||
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||
@ -38,7 +41,7 @@ function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||
}, {}) : {};
|
||||
}
|
||||
|
||||
export function buildApiConfig(json: GramJs.TypeJSONValue): ApiAppConfig {
|
||||
export function buildAppConfig(json: GramJs.TypeJSONValue): ApiAppConfig {
|
||||
const appConfig = buildJson(json) as GramJsAppConfig;
|
||||
|
||||
return {
|
||||
@ -46,5 +49,8 @@ export function buildApiConfig(json: GramJs.TypeJSONValue): ApiAppConfig {
|
||||
defaultReaction: appConfig.reactions_default,
|
||||
seenByMaxChatMembers: appConfig.chat_read_mark_size_threshold,
|
||||
seenByExpiresAt: appConfig.chat_read_mark_expire_period,
|
||||
autologinDomains: appConfig.autologin_domains || [],
|
||||
autologinToken: appConfig.autologin_token || '',
|
||||
urlAuthDomains: appConfig.url_auth_domains || [],
|
||||
};
|
||||
}
|
||||
|
@ -1104,6 +1104,15 @@ function buildReplyButtons(message: UniversalMessage): ApiReplyKeyboard | undefi
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonUrlAuth) {
|
||||
return {
|
||||
type: 'urlAuth',
|
||||
text,
|
||||
url: button.url,
|
||||
buttonId: button.buttonId,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'unsupported',
|
||||
text,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type {
|
||||
ApiCountry, ApiSession, ApiWallpaper,
|
||||
ApiCountry, ApiSession, ApiUrlAuthResult, ApiWallpaper, ApiWebSession,
|
||||
} from '../../types';
|
||||
import type { ApiPrivacySettings, ApiPrivacyKey, PrivacyVisibility } from '../../../types';
|
||||
|
||||
@ -9,6 +9,8 @@ import { buildApiDocument } from './messages';
|
||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { buildApiUser } from './users';
|
||||
import { addUserToLocalDb } from '../helpers';
|
||||
|
||||
export function buildApiWallpaper(wallpaper: GramJs.TypeWallPaper): ApiWallpaper | undefined {
|
||||
if (wallpaper instanceof GramJs.WallPaperNoFile) {
|
||||
@ -45,6 +47,16 @@ export function buildApiSession(session: GramJs.Authorization): ApiSession {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiWebSession(session: GramJs.WebAuthorization): ApiWebSession {
|
||||
return {
|
||||
hash: String(session.hash),
|
||||
botId: buildApiPeerId(session.botId, 'user'),
|
||||
...pick(session, [
|
||||
'platform', 'browser', 'dateCreated', 'dateActive', 'ip', 'region', 'domain',
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | undefined {
|
||||
switch (key.className) {
|
||||
case 'PrivacyKeyPhoneNumber':
|
||||
@ -176,3 +188,34 @@ export function buildJson(json: GramJs.TypeJSONValue): any {
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlAuthResult | undefined {
|
||||
if (result instanceof GramJs.UrlAuthResultRequest) {
|
||||
const { bot, domain, requestWriteAccess } = result;
|
||||
const user = buildApiUser(bot);
|
||||
if (!user) return undefined;
|
||||
|
||||
addUserToLocalDb(bot);
|
||||
|
||||
return {
|
||||
type: 'request',
|
||||
domain,
|
||||
shouldRequestWriteAccess: requestWriteAccess,
|
||||
bot: user,
|
||||
};
|
||||
}
|
||||
|
||||
if (result instanceof GramJs.UrlAuthResultAccepted) {
|
||||
return {
|
||||
type: 'accepted',
|
||||
url: result.url,
|
||||
};
|
||||
}
|
||||
|
||||
if (result instanceof GramJs.UrlAuthResultDefault) {
|
||||
return {
|
||||
type: 'default',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import BigInt from 'big-integer';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type { ApiChat, ApiThemeParameters, ApiUser } from '../../types';
|
||||
import type {
|
||||
ApiChat, ApiThemeParameters, ApiUser, OnApiUpdate,
|
||||
} from '../../types';
|
||||
|
||||
import localDb from '../localDb';
|
||||
import { invokeRequest } from './client';
|
||||
@ -14,8 +16,12 @@ import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
|
||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { buildApiUrlAuthResult } from '../apiBuilders/misc';
|
||||
|
||||
export function init() {
|
||||
let onUpdate: OnApiUpdate;
|
||||
|
||||
export function init(_onUpdate: OnApiUpdate) {
|
||||
onUpdate = _onUpdate;
|
||||
}
|
||||
|
||||
export async function answerCallbackButton({
|
||||
@ -269,6 +275,100 @@ export function toggleBotInAttachMenu({
|
||||
}));
|
||||
}
|
||||
|
||||
export async function requestBotUrlAuth({
|
||||
chat, buttonId, messageId,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
buttonId: number;
|
||||
messageId: number;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.messages.RequestUrlAuth({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
buttonId,
|
||||
msgId: messageId,
|
||||
}));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
const authResult = buildApiUrlAuthResult(result);
|
||||
if (authResult?.type === 'request') {
|
||||
onUpdate({
|
||||
'@type': 'updateUser',
|
||||
id: authResult.bot.id,
|
||||
user: authResult.bot,
|
||||
});
|
||||
}
|
||||
return authResult;
|
||||
}
|
||||
|
||||
export async function acceptBotUrlAuth({
|
||||
chat,
|
||||
messageId,
|
||||
buttonId,
|
||||
isWriteAllowed,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
messageId: number;
|
||||
buttonId: number;
|
||||
isWriteAllowed?: boolean;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.messages.AcceptUrlAuth({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
msgId: messageId,
|
||||
buttonId,
|
||||
writeAllowed: isWriteAllowed || undefined,
|
||||
}));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
const authResult = buildApiUrlAuthResult(result);
|
||||
if (authResult?.type === 'request') {
|
||||
onUpdate({
|
||||
'@type': 'updateUser',
|
||||
id: authResult.bot.id,
|
||||
user: authResult.bot,
|
||||
});
|
||||
}
|
||||
return authResult;
|
||||
}
|
||||
|
||||
export async function requestLinkUrlAuth({ url }: { url: string }) {
|
||||
const result = await invokeRequest(new GramJs.messages.RequestUrlAuth({
|
||||
url,
|
||||
}));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
const authResult = buildApiUrlAuthResult(result);
|
||||
if (authResult?.type === 'request') {
|
||||
onUpdate({
|
||||
'@type': 'updateUser',
|
||||
id: authResult.bot.id,
|
||||
user: authResult.bot,
|
||||
});
|
||||
}
|
||||
return authResult;
|
||||
}
|
||||
|
||||
export async function acceptLinkUrlAuth({ url, isWriteAllowed }: { url: string; isWriteAllowed?: boolean }) {
|
||||
const result = await invokeRequest(new GramJs.messages.AcceptUrlAuth({
|
||||
url,
|
||||
writeAllowed: isWriteAllowed || undefined,
|
||||
}));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
const authResult = buildApiUrlAuthResult(result);
|
||||
if (authResult?.type === 'request') {
|
||||
onUpdate({
|
||||
'@type': 'updateUser',
|
||||
id: authResult.bot.id,
|
||||
user: authResult.bot,
|
||||
});
|
||||
}
|
||||
return authResult;
|
||||
}
|
||||
|
||||
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
||||
return results.map((result) => {
|
||||
if (result instanceof GramJs.BotInlineMediaResult) {
|
||||
|
@ -54,6 +54,7 @@ export {
|
||||
updateProfile, checkUsername, updateUsername, fetchBlockedContacts, blockContact, unblockContact,
|
||||
updateProfilePhoto, uploadProfilePhoto, fetchWallpapers, uploadWallpaper,
|
||||
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
|
||||
fetchWebAuthorizations, terminateWebAuthorization, terminateAllWebAuthorizations,
|
||||
fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings,
|
||||
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings, registerDevice, unregisterDevice,
|
||||
updateIsOnline, fetchContentSettings, updateContentSettings, fetchLangStrings, fetchCountryList, fetchAppConfig,
|
||||
@ -66,6 +67,7 @@ export {
|
||||
export {
|
||||
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachMenuBots, toggleBotInAttachMenu,
|
||||
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth,
|
||||
} from './bots';
|
||||
|
||||
export {
|
||||
@ -86,7 +88,7 @@ export {
|
||||
|
||||
export {
|
||||
fetchChannelStatistics, fetchGroupStatistics, fetchMessageStatistics,
|
||||
fetchMessagePublicForwards, fetchStatisticsAsyncGraph,
|
||||
fetchMessagePublicForwards, fetchStatisticsAsyncGraph,
|
||||
} from './statistics';
|
||||
|
||||
export {
|
||||
|
@ -19,20 +19,21 @@ import {
|
||||
buildApiNotifyException,
|
||||
buildApiSession,
|
||||
buildApiWallpaper,
|
||||
buildApiWebSession,
|
||||
buildPrivacyRules,
|
||||
} from '../apiBuilders/misc';
|
||||
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||
import { buildAppConfig } from '../apiBuilders/appConfig';
|
||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||
import { buildInputEntity, buildInputPeer, buildInputPrivacyKey } from '../gramjsBuilders';
|
||||
import { getClient, invokeRequest, uploadFile } from './client';
|
||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||
import localDb from '../localDb';
|
||||
import { buildApiConfig } from '../apiBuilders/appConfig';
|
||||
import { addEntitiesWithPhotosToLocalDb } from '../helpers';
|
||||
import localDb from '../localDb';
|
||||
|
||||
const MAX_INT_32 = 2 ** 31 - 1;
|
||||
const BETA_LANG_CODES = ['ar', 'fa', 'id', 'ko', 'uz', 'en'];
|
||||
@ -175,6 +176,23 @@ export function terminateAllAuthorizations() {
|
||||
return invokeRequest(new GramJs.auth.ResetAuthorizations());
|
||||
}
|
||||
|
||||
export async function fetchWebAuthorizations() {
|
||||
const result = await invokeRequest(new GramJs.account.GetWebAuthorizations());
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildCollectionByKey(result.authorizations.map(buildApiWebSession), 'hash');
|
||||
}
|
||||
|
||||
export function terminateWebAuthorization(hash: string) {
|
||||
return invokeRequest(new GramJs.account.ResetWebAuthorization({ hash: BigInt(hash) }));
|
||||
}
|
||||
|
||||
export function terminateAllWebAuthorizations() {
|
||||
return invokeRequest(new GramJs.account.ResetWebAuthorizations());
|
||||
}
|
||||
|
||||
export async function fetchNotificationExceptions({
|
||||
serverTimeOffset,
|
||||
}: { serverTimeOffset: number }) {
|
||||
@ -451,7 +469,7 @@ export async function fetchAppConfig(): Promise<ApiAppConfig | undefined> {
|
||||
const result = await invokeRequest(new GramJs.help.GetAppConfig());
|
||||
if (!result) return undefined;
|
||||
|
||||
return buildApiConfig(result);
|
||||
return buildAppConfig(result);
|
||||
}
|
||||
|
||||
function updateLocalDb(
|
||||
|
@ -17,6 +17,7 @@ import { init as initClient } from './methods/client';
|
||||
import { init as initStickers } from './methods/symbols';
|
||||
import { init as initManagement } from './methods/management';
|
||||
import { init as initTwoFaSettings } from './methods/twoFaSettings';
|
||||
import { init as initBots } from './methods/bots';
|
||||
import { init as initCalls } from './methods/calls';
|
||||
import { init as initPayments } from './methods/payments';
|
||||
import * as methods from './methods';
|
||||
@ -34,6 +35,7 @@ export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArg
|
||||
initStickers(handleUpdate);
|
||||
initManagement(handleUpdate);
|
||||
initTwoFaSettings(handleUpdate);
|
||||
initBots(handleUpdate);
|
||||
initCalls(handleUpdate);
|
||||
initPayments(handleUpdate);
|
||||
|
||||
|
@ -438,6 +438,13 @@ interface ApiKeyboardButtonUserProfile {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
interface ApiKeyboardButtonUrlAuth {
|
||||
type: 'urlAuth';
|
||||
text: string;
|
||||
url: string;
|
||||
buttonId: number;
|
||||
}
|
||||
|
||||
export type ApiKeyboardButton = (
|
||||
ApiKeyboardButtonSimple
|
||||
| ApiKeyboardButtonReceipt
|
||||
@ -448,6 +455,7 @@ export type ApiKeyboardButton = (
|
||||
| ApiKeyboardButtonUserProfile
|
||||
| ApiKeyboardButtonWebView
|
||||
| ApiKeyboardButtonSimpleWebView
|
||||
| ApiKeyboardButtonUrlAuth
|
||||
);
|
||||
|
||||
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { ApiDocument, ApiPhoto } from './messages';
|
||||
import type { ApiUser } from './users';
|
||||
|
||||
export interface ApiInitialArgs {
|
||||
userAgent: string;
|
||||
@ -65,6 +66,18 @@ export interface ApiSession {
|
||||
areSecretChatsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ApiWebSession {
|
||||
hash: string;
|
||||
botId: string;
|
||||
domain: string;
|
||||
browser: string;
|
||||
platform: string;
|
||||
dateCreated: number;
|
||||
dateActive: number;
|
||||
ip: string;
|
||||
region: string;
|
||||
}
|
||||
|
||||
export interface ApiSessionData {
|
||||
mainDcId: number;
|
||||
keys: Record<number, string | number[]>;
|
||||
@ -145,6 +158,9 @@ export interface ApiAppConfig {
|
||||
defaultReaction: string;
|
||||
seenByMaxChatMembers: number;
|
||||
seenByExpiresAt: number;
|
||||
autologinDomains: string[];
|
||||
autologinToken: string;
|
||||
urlAuthDomains: string[];
|
||||
}
|
||||
|
||||
export interface GramJsEmojiInteraction {
|
||||
@ -158,3 +174,21 @@ export interface GramJsEmojiInteraction {
|
||||
export interface ApiEmojiInteraction {
|
||||
timestamps: number[];
|
||||
}
|
||||
|
||||
type ApiUrlAuthResultRequest = {
|
||||
type: 'request';
|
||||
bot: ApiUser;
|
||||
domain: string;
|
||||
shouldRequestWriteAccess?: boolean;
|
||||
};
|
||||
|
||||
type ApiUrlAuthResultAccepted = {
|
||||
type: 'accepted';
|
||||
url: string;
|
||||
};
|
||||
|
||||
type ApiUrlAuthResultDefault = {
|
||||
type: 'default';
|
||||
};
|
||||
|
||||
export type ApiUrlAuthResult = ApiUrlAuthResultRequest | ApiUrlAuthResultAccepted | ApiUrlAuthResultDefault;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { ApiChat } from './chats';
|
||||
import type { ApiMessage, ApiPhoto } from './messages';
|
||||
import type { ApiMessage } from './messages';
|
||||
|
||||
export interface ApiChannelStatistics {
|
||||
growthGraph?: StatisticsGraph | string;
|
||||
|
Binary file not shown.
Binary file not shown.
@ -5,6 +5,7 @@ export { default as ForwardPicker } from '../components/main/ForwardPicker';
|
||||
export { default as Dialogs } from '../components/main/Dialogs';
|
||||
export { default as Notifications } from '../components/main/Notifications';
|
||||
export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
|
||||
export { default as UrlAuthModal } from '../components/main/UrlAuthModal';
|
||||
export { default as HistoryCalendar } from '../components/main/HistoryCalendar';
|
||||
export { default as NewContactModal } from '../components/main/NewContactModal';
|
||||
export { default as WebAppModal } from '../components/main/WebAppModal';
|
||||
|
@ -1,7 +1,10 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useRef, useState } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useRef, useState, useCallback,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
@ -41,6 +44,10 @@ const RatePhoneCallModal: FC<OwnProps> = ({
|
||||
return () => setRating(rating === index ? undefined : index);
|
||||
}
|
||||
|
||||
const handleCancelClick = useCallback(() => {
|
||||
closeCallRatingModal();
|
||||
}, [closeCallRatingModal]);
|
||||
|
||||
return (
|
||||
<Modal title={lang('lng_call_rate_label')} className="narrow" onClose={closeCallRatingModal} isOpen={isOpen}>
|
||||
<div className={styles.stars}>
|
||||
@ -68,7 +75,7 @@ const RatePhoneCallModal: FC<OwnProps> = ({
|
||||
<Button className="confirm-dialog-button" isText onClick={handleSend}>
|
||||
{lang('Send')}
|
||||
</Button>
|
||||
<Button className="confirm-dialog-button" isText onClick={closeCallRatingModal}>{lang('Cancel')}</Button>
|
||||
<Button className="confirm-dialog-button" isText onClick={handleCancelClick}>{lang('Cancel')}</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import { getActions } from '../../global';
|
||||
import convertPunycode from '../../lib/punycode';
|
||||
|
||||
import {
|
||||
DEBUG, RE_TG_LINK, RE_TME_LINK,
|
||||
DEBUG,
|
||||
} from '../../config';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { ensureProtocol } from '../../util/ensureProtocol';
|
||||
@ -24,31 +24,18 @@ const SafeLink: FC<OwnProps> = ({
|
||||
children,
|
||||
isRtl,
|
||||
}) => {
|
||||
const { toggleSafeLinkModal, openTelegramLink } = getActions();
|
||||
const { openUrl } = getActions();
|
||||
|
||||
const content = children || text;
|
||||
const isNotSafe = url !== content;
|
||||
const isSafe = url === content;
|
||||
|
||||
const handleClick = useCallback((e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
if (
|
||||
e.ctrlKey || e.altKey || e.shiftKey || e.metaKey
|
||||
|| !url || (!url.match(RE_TME_LINK) && !url.match(RE_TG_LINK))
|
||||
) {
|
||||
if (isNotSafe) {
|
||||
toggleSafeLinkModal({ url });
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!url) return true;
|
||||
e.preventDefault();
|
||||
openTelegramLink({ url });
|
||||
openUrl({ url, shouldSkipModal: isSafe });
|
||||
|
||||
return false;
|
||||
}, [isNotSafe, openTelegramLink, toggleSafeLinkModal, url]);
|
||||
}, [isSafe, openUrl, url]);
|
||||
|
||||
if (!url) {
|
||||
return undefined;
|
||||
|
@ -161,6 +161,7 @@ const LeftColumn: FC<StateProps> = ({
|
||||
case SettingsScreens.PrivacyForwarding:
|
||||
case SettingsScreens.PrivacyGroupChats:
|
||||
case SettingsScreens.PrivacyBlockedUsers:
|
||||
case SettingsScreens.ActiveWebsites:
|
||||
case SettingsScreens.TwoFaDisabled:
|
||||
case SettingsScreens.TwoFaEnabled:
|
||||
case SettingsScreens.TwoFaCongratulations:
|
||||
|
@ -208,7 +208,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
}, [animationLevel, setSettingOption, theme]);
|
||||
|
||||
const handleChangelogClick = useCallback(() => {
|
||||
window.open(BETA_CHANGELOG_URL, '_blank');
|
||||
window.open(BETA_CHANGELOG_URL, '_blank', 'noopener');
|
||||
}, []);
|
||||
|
||||
const handleRuDiscussionClick = useCallback(() => {
|
||||
|
@ -182,6 +182,7 @@
|
||||
|
||||
&.full-size {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.date {
|
||||
|
@ -21,6 +21,7 @@ import SettingsPrivacy from './SettingsPrivacy';
|
||||
import SettingsLanguage from './SettingsLanguage';
|
||||
import SettingsPrivacyVisibility from './SettingsPrivacyVisibility';
|
||||
import SettingsActiveSessions from './SettingsActiveSessions';
|
||||
import SettingsActiveWebsites from './SettingsActiveWebsites';
|
||||
import SettingsPrivacyBlockedUsers from './SettingsPrivacyBlockedUsers';
|
||||
import SettingsTwoFa from './twoFa/SettingsTwoFa';
|
||||
import SettingsPrivacyVisibilityExceptionList from './SettingsPrivacyVisibilityExceptionList';
|
||||
@ -69,7 +70,7 @@ const FOLDERS_SCREENS = [
|
||||
|
||||
const PRIVACY_SCREENS = [
|
||||
SettingsScreens.PrivacyBlockedUsers,
|
||||
SettingsScreens.ActiveSessions,
|
||||
SettingsScreens.ActiveWebsites,
|
||||
];
|
||||
|
||||
const PRIVACY_PHONE_NUMBER_SCREENS = [
|
||||
@ -258,6 +259,13 @@ const Settings: FC<OwnProps> = ({
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.ActiveWebsites:
|
||||
return (
|
||||
<SettingsActiveWebsites
|
||||
isActive={isScreenActive}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.PrivacyBlockedUsers:
|
||||
return (
|
||||
<SettingsPrivacyBlockedUsers
|
||||
|
@ -9,7 +9,7 @@ $icons: "android", "apple", "brave", "chrome", "edge", "firefox", "linux", "oper
|
||||
.SettingsActiveSessions {
|
||||
.icon-device {
|
||||
width: 2rem;
|
||||
height:2rem;
|
||||
height: 2rem;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 2rem;
|
||||
flex: 0 0 2rem;
|
||||
|
@ -195,6 +195,7 @@ const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
|
||||
contextActions={[{
|
||||
title: 'Terminate',
|
||||
icon: 'stop',
|
||||
destructive: true,
|
||||
handler: () => {
|
||||
handleTerminateSessionClick(session.hash);
|
||||
},
|
||||
|
@ -0,0 +1,48 @@
|
||||
.root {
|
||||
:global(.modal-dialog) {
|
||||
max-width: 28rem;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
font-size: 3.5rem;
|
||||
margin: 0 auto 1rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
:global(.Avatar__img) {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.note,
|
||||
.date {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box {
|
||||
background: var(--color-background-secondary);
|
||||
padding: 1rem 1rem 0.5rem;
|
||||
border-radius: var(--border-radius-default);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.action-header {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.action-name {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.header-button {
|
||||
margin-right: -0.5rem;
|
||||
}
|
103
src/components/left/settings/SettingsActiveWebsite.tsx
Normal file
103
src/components/left/settings/SettingsActiveWebsite.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ApiUser, ApiWebSession } from '../../../api/types';
|
||||
|
||||
import { getUserFullName } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||
|
||||
import Modal from '../../ui/Modal';
|
||||
import Button from '../../ui/Button';
|
||||
import Avatar from '../../common/Avatar';
|
||||
|
||||
import styles from './SettingsActiveWebsite.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
isOpen: boolean;
|
||||
hash?: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
session?: ApiWebSession;
|
||||
bot?: ApiUser;
|
||||
};
|
||||
|
||||
const SettingsActiveWebsite: FC<OwnProps & StateProps> = ({
|
||||
isOpen, session, bot, onClose,
|
||||
}) => {
|
||||
const { terminateWebAuthorization } = getActions();
|
||||
const lang = useLang();
|
||||
|
||||
const renderingSession = useCurrentOrPrev(session, true);
|
||||
const renderingBot = useCurrentOrPrev(bot, true);
|
||||
|
||||
const handleTerminateSessionClick = useCallback(() => {
|
||||
terminateWebAuthorization({ hash: session!.hash });
|
||||
onClose();
|
||||
}, [onClose, session, terminateWebAuthorization]);
|
||||
|
||||
if (!renderingSession) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function renderHeader() {
|
||||
return (
|
||||
<div className="modal-header-condensed" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button round color="translucent" size="smaller" ariaLabel={lang('Close')} onClick={onClose}>
|
||||
<i className="icon-close" />
|
||||
</Button>
|
||||
<div className="modal-title">{lang('WebSessionsTitle')}</div>
|
||||
<Button
|
||||
color="danger"
|
||||
onClick={handleTerminateSessionClick}
|
||||
className={buildClassName('modal-action-button', styles.headerButton)}
|
||||
>
|
||||
{lang('AuthSessions.LogOut')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
header={renderHeader()}
|
||||
isOpen={isOpen}
|
||||
hasCloseButton
|
||||
onClose={onClose}
|
||||
className={styles.root}
|
||||
>
|
||||
<Avatar className={styles.avatar} user={renderingBot} size="large" />
|
||||
<h3 className={styles.title} dir="auto">{getUserFullName(renderingBot)}</h3>
|
||||
<div className={styles.date} aria-label={lang('PrivacySettings.LastSeen')}>
|
||||
{renderingSession?.domain}
|
||||
</div>
|
||||
|
||||
<dl className={styles.box}>
|
||||
<dt>{lang('AuthSessions.View.Browser')}</dt>
|
||||
<dd>
|
||||
{renderingSession?.browser}
|
||||
</dd>
|
||||
|
||||
<dt>{lang('SessionPreview.Ip')}</dt>
|
||||
<dd>{renderingSession?.ip}</dd>
|
||||
|
||||
<dt>{lang('SessionPreview.Location')}</dt>
|
||||
<dd>{renderingSession?.region}</dd>
|
||||
</dl>
|
||||
<p className={styles.note}>{lang('AuthSessions.View.LocationInfo')}</p>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global, { hash }) => {
|
||||
const session = hash ? global.activeWebSessions.byHash[hash] : undefined;
|
||||
const bot = session ? global.users.byId[session.botId] : undefined;
|
||||
return {
|
||||
session,
|
||||
bot,
|
||||
};
|
||||
})(SettingsActiveWebsite));
|
@ -0,0 +1,21 @@
|
||||
.avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin-inline-end: 1.5rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
:global(.Avatar__img) {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.clear-help {
|
||||
margin-top: 0.5rem !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
:global(.subtitle) {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
166
src/components/left/settings/SettingsActiveWebsites.tsx
Normal file
166
src/components/left/settings/SettingsActiveWebsites.tsx
Normal file
@ -0,0 +1,166 @@
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ApiWebSession } from '../../../api/types';
|
||||
|
||||
import { formatPastTimeShort } from '../../../util/dateFormat';
|
||||
import { getUserFullName } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
import SettingsActiveWebsite from './SettingsActiveWebsite';
|
||||
import Avatar from '../../common/Avatar';
|
||||
|
||||
import styles from './SettingsActiveWebsites.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
byHash: Record<string, ApiWebSession>;
|
||||
orderedHashes: string[];
|
||||
};
|
||||
|
||||
const SettingsActiveWebsites: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
byHash,
|
||||
orderedHashes,
|
||||
onReset,
|
||||
}) => {
|
||||
const {
|
||||
terminateWebAuthorization,
|
||||
terminateAllWebAuthorizations,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const [isConfirmTerminateAllDialogOpen, openConfirmTerminateAllDialog, closeConfirmTerminateAllDialog] = useFlag();
|
||||
const [openedWebsiteHash, setOpenedWebsiteHash] = useState<string | undefined>();
|
||||
const [isModalOpen, openModal, closeModal] = useFlag();
|
||||
|
||||
const handleTerminateAuthClick = useCallback((hash: string) => {
|
||||
terminateWebAuthorization({ hash });
|
||||
}, [terminateWebAuthorization]);
|
||||
|
||||
const handleTerminateAllAuth = useCallback(() => {
|
||||
closeConfirmTerminateAllDialog();
|
||||
terminateAllWebAuthorizations();
|
||||
}, [closeConfirmTerminateAllDialog, terminateAllWebAuthorizations]);
|
||||
|
||||
const handleOpenSessionModal = useCallback((hash: string) => {
|
||||
setOpenedWebsiteHash(hash);
|
||||
openModal();
|
||||
}, [openModal]);
|
||||
|
||||
const handleCloseWebsiteModal = useCallback(() => {
|
||||
setOpenedWebsiteHash(undefined);
|
||||
closeModal();
|
||||
}, [closeModal]);
|
||||
|
||||
// Close when empty
|
||||
useEffect(() => {
|
||||
if (!orderedHashes.length) {
|
||||
onReset();
|
||||
}
|
||||
}, [onReset, orderedHashes]);
|
||||
|
||||
useHistoryBack({
|
||||
isActive,
|
||||
onBack: onReset,
|
||||
});
|
||||
|
||||
function renderSessions(sessionHashes: string[]) {
|
||||
return (
|
||||
<div className="settings-item">
|
||||
<h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('WebSessionsTitle')}
|
||||
</h4>
|
||||
|
||||
{sessionHashes.map(renderSession)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSession(sessionHash: string) {
|
||||
const session = byHash[sessionHash];
|
||||
const bot = getGlobal().users.byId[session.botId];
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={session.hash}
|
||||
ripple
|
||||
narrow
|
||||
contextActions={[{
|
||||
title: 'Terminate',
|
||||
icon: 'stop',
|
||||
destructive: true,
|
||||
handler: () => {
|
||||
handleTerminateAuthClick(session.hash);
|
||||
},
|
||||
}]}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => handleOpenSessionModal(session.hash)}
|
||||
>
|
||||
<Avatar className={styles.avatar} user={bot} size="tiny" />
|
||||
<div className="multiline-menu-item full-size" dir="auto">
|
||||
<span className="date">{formatPastTimeShort(lang, session.dateActive * 1000)}</span>
|
||||
<span className="title">{getUserFullName(bot)}</span>
|
||||
<span className={buildClassName('subtitle', 'black', 'tight', styles.platform)}>
|
||||
{session.domain}, {session.browser}, {session.platform}
|
||||
</span>
|
||||
<span className="subtitle">{session.ip} {session.region}</span>
|
||||
</div>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (!orderedHashes.length) return undefined;
|
||||
|
||||
return (
|
||||
<div className="settings-content custom-scroll">
|
||||
<div className="settings-item">
|
||||
<ListItem
|
||||
className="destructive mb-0 no-icon"
|
||||
icon="stop"
|
||||
ripple
|
||||
narrow
|
||||
onClick={openConfirmTerminateAllDialog}
|
||||
>
|
||||
{lang('AuthSessions.LogOutApplications')}
|
||||
</ListItem>
|
||||
<p className={buildClassName('settings-item-description', styles.clearHelp)}>
|
||||
{lang('ClearOtherWebSessionsHelp')}
|
||||
</p>
|
||||
</div>
|
||||
{renderSessions(orderedHashes)}
|
||||
<ConfirmDialog
|
||||
isOpen={isConfirmTerminateAllDialogOpen}
|
||||
onClose={closeConfirmTerminateAllDialog}
|
||||
title={lang('AuthSessions.LogOutApplications')}
|
||||
text={lang('AreYouSureWebSessions')}
|
||||
confirmHandler={handleTerminateAllAuth}
|
||||
confirmIsDestructive
|
||||
/>
|
||||
<SettingsActiveWebsite isOpen={isModalOpen} hash={openedWebsiteHash} onClose={handleCloseWebsiteModal} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { byHash, orderedHashes } = global.activeWebSessions;
|
||||
return {
|
||||
byHash,
|
||||
orderedHashes,
|
||||
};
|
||||
},
|
||||
)(SettingsActiveWebsites));
|
@ -129,6 +129,8 @@ const SettingsHeader: FC<OwnProps> = ({
|
||||
|
||||
case SettingsScreens.ActiveSessions:
|
||||
return <h3>{lang('SessionsTitle')}</h3>;
|
||||
case SettingsScreens.ActiveWebsites:
|
||||
return <h3>{lang('OtherWebSessions')}</h3>;
|
||||
case SettingsScreens.PrivacyBlockedUsers:
|
||||
return <h3>{lang('BlockedUsers')}</h3>;
|
||||
|
||||
|
@ -33,7 +33,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
sessionCount,
|
||||
lastSyncTime,
|
||||
}) => {
|
||||
const { loadProfilePhotos, loadAuthorizations } = getActions();
|
||||
const { loadProfilePhotos, loadAuthorizations, loadWebAuthorizations } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const profileId = currentUser?.id;
|
||||
@ -52,8 +52,9 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
useEffect(() => {
|
||||
if (lastSyncTime) {
|
||||
loadAuthorizations();
|
||||
loadWebAuthorizations();
|
||||
}
|
||||
}, [lastSyncTime, loadAuthorizations]);
|
||||
}, [lastSyncTime, loadAuthorizations, loadWebAuthorizations]);
|
||||
|
||||
return (
|
||||
<div className="settings-content custom-scroll">
|
||||
|
@ -21,6 +21,7 @@ type StateProps = {
|
||||
hasPassword?: boolean;
|
||||
hasPasscode?: boolean;
|
||||
blockedCount: number;
|
||||
webAuthCount: number;
|
||||
isSensitiveEnabled?: boolean;
|
||||
canChangeSensitive?: boolean;
|
||||
privacyPhoneNumber?: ApiPrivacySettings;
|
||||
@ -34,11 +35,10 @@ type StateProps = {
|
||||
|
||||
const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
hasPassword,
|
||||
hasPasscode,
|
||||
blockedCount,
|
||||
webAuthCount,
|
||||
isSensitiveEnabled,
|
||||
canChangeSensitive,
|
||||
privacyPhoneNumber,
|
||||
@ -48,7 +48,8 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
privacyGroupChats,
|
||||
privacyPhoneCall,
|
||||
privacyPhoneP2P,
|
||||
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
}) => {
|
||||
const {
|
||||
loadPrivacySettings,
|
||||
@ -114,6 +115,16 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</ListItem>
|
||||
{webAuthCount > 0 && (
|
||||
<ListItem
|
||||
icon="web"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.ActiveWebsites)}
|
||||
>
|
||||
{lang('PrivacySettings.WebSessions')}
|
||||
<span className="settings-item__current-value">{webAuthCount}</span>
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem
|
||||
icon="key"
|
||||
narrow
|
||||
@ -279,6 +290,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
hasPassword,
|
||||
hasPasscode: Boolean(hasPasscode),
|
||||
blockedCount: blocked.totalCount,
|
||||
webAuthCount: global.activeWebSessions.orderedHashes.length,
|
||||
isSensitiveEnabled,
|
||||
canChangeSensitive,
|
||||
privacyPhoneNumber: privacy.phoneNumber,
|
||||
|
@ -6,7 +6,7 @@ import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type { LangCode } from '../../types';
|
||||
import type {
|
||||
ApiChat, ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType,
|
||||
ApiChat, ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType, ApiUser,
|
||||
} from '../../api/types';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
selectIsMediaViewerOpen,
|
||||
selectIsRightColumnShown,
|
||||
selectIsServiceChatReady,
|
||||
selectUser,
|
||||
} from '../../global/selectors';
|
||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
@ -61,6 +62,7 @@ import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async';
|
||||
import WebAppModal from './WebAppModal.async';
|
||||
import BotTrustModal from './BotTrustModal.async';
|
||||
import BotAttachModal from './BotAttachModal.async';
|
||||
import UrlAuthModal from './UrlAuthModal.async';
|
||||
|
||||
import './Main.scss';
|
||||
|
||||
@ -95,6 +97,8 @@ type StateProps = {
|
||||
webApp?: GlobalState['webApp'];
|
||||
botTrustRequest?: GlobalState['botTrustRequest'];
|
||||
botAttachRequest?: GlobalState['botAttachRequest'];
|
||||
currentUser?: ApiUser;
|
||||
urlAuth?: GlobalState['urlAuth'];
|
||||
};
|
||||
|
||||
const NOTIFICATION_INTERVAL = 1000;
|
||||
@ -134,6 +138,8 @@ const Main: FC<StateProps> = ({
|
||||
botTrustRequest,
|
||||
botAttachRequest,
|
||||
webApp,
|
||||
currentUser,
|
||||
urlAuth,
|
||||
}) => {
|
||||
const {
|
||||
sync,
|
||||
@ -369,6 +375,7 @@ const Main: FC<StateProps> = ({
|
||||
<Dialogs isOpen={hasDialogs} />
|
||||
{audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />}
|
||||
<SafeLinkModal url={safeLinkModalUrl} />
|
||||
<UrlAuthModal urlAuth={urlAuth} currentUser={currentUser} />
|
||||
<HistoryCalendar isOpen={isHistoryCalendarOpen} />
|
||||
<StickerSetModal
|
||||
isOpen={Boolean(openedStickerSetShortName)}
|
||||
@ -432,6 +439,7 @@ export default memo(withGlobal(
|
||||
const openedGame = global.openedGame;
|
||||
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
|
||||
const gameTitle = gameMessage?.content.game?.title;
|
||||
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
||||
|
||||
return {
|
||||
connectionState: global.connectionState,
|
||||
@ -463,6 +471,8 @@ export default memo(withGlobal(
|
||||
botTrustRequest: global.botTrustRequest,
|
||||
botAttachRequest: global.botAttachRequest,
|
||||
webApp: global.webApp,
|
||||
currentUser,
|
||||
urlAuth: global.urlAuth,
|
||||
};
|
||||
},
|
||||
)(Main));
|
||||
|
@ -19,7 +19,7 @@ const SafeLinkModal: FC<OwnProps> = ({ url }) => {
|
||||
const lang = useLang();
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
window.open(ensureProtocol(url));
|
||||
window.open(ensureProtocol(url), '_blank', 'noopener');
|
||||
toggleSafeLinkModal({ url: undefined });
|
||||
}, [toggleSafeLinkModal, url]);
|
||||
|
||||
|
17
src/components/main/UrlAuthModal.async.tsx
Normal file
17
src/components/main/UrlAuthModal.async.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { OwnProps } from './UrlAuthModal';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const UrlAuthModalAsync: FC<OwnProps> = (props) => {
|
||||
const { urlAuth } = props;
|
||||
const UrlAuthModal = useModuleLoader(Bundles.Extra, 'UrlAuthModal', !urlAuth);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return UrlAuthModal ? <UrlAuthModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(UrlAuthModalAsync);
|
3
src/components/main/UrlAuthModal.module.scss
Normal file
3
src/components/main/UrlAuthModal.module.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.checkbox {
|
||||
margin: 1rem 0;
|
||||
}
|
114
src/components/main/UrlAuthModal.tsx
Normal file
114
src/components/main/UrlAuthModal.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, getGlobal } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { ApiUser } from '../../api/types';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
|
||||
import { ensureProtocol } from '../../util/ensureProtocol';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
import { getUserFullName } from '../../global/helpers';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
|
||||
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
|
||||
import styles from './UrlAuthModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
urlAuth?: GlobalState['urlAuth'];
|
||||
currentUser?: ApiUser;
|
||||
};
|
||||
|
||||
const UrlAuthModal: FC<OwnProps> = ({
|
||||
urlAuth, currentUser,
|
||||
}) => {
|
||||
const { closeUrlAuthModal, acceptBotUrlAuth, acceptLinkUrlAuth } = getActions();
|
||||
const [isLoginChecked, setLoginChecked] = useState(true);
|
||||
const [isWriteAccessChecked, setWriteAccessChecked] = useState(true);
|
||||
const currentAuth = useCurrentOrPrev(urlAuth, false);
|
||||
const { domain, botId, shouldRequestWriteAccess } = currentAuth?.request || {};
|
||||
const bot = botId ? getGlobal().users.byId[botId] : undefined;
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
if (urlAuth?.url && isLoginChecked) {
|
||||
const acceptAction = urlAuth.button ? acceptBotUrlAuth : acceptLinkUrlAuth;
|
||||
acceptAction({
|
||||
isWriteAllowed: isWriteAccessChecked,
|
||||
});
|
||||
} else {
|
||||
window.open(ensureProtocol(currentAuth?.url), '_blank', 'noopener');
|
||||
}
|
||||
closeUrlAuthModal();
|
||||
}, [
|
||||
urlAuth, isLoginChecked, closeUrlAuthModal, acceptBotUrlAuth, acceptLinkUrlAuth, isWriteAccessChecked, currentAuth,
|
||||
]);
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
closeUrlAuthModal();
|
||||
}, [closeUrlAuthModal]);
|
||||
|
||||
const handleLoginChecked = useCallback((value: boolean) => {
|
||||
setLoginChecked(value);
|
||||
setWriteAccessChecked(value);
|
||||
}, [setLoginChecked]);
|
||||
|
||||
// Reset on re-open
|
||||
useEffect(() => {
|
||||
if (domain) {
|
||||
setLoginChecked(true);
|
||||
setWriteAccessChecked(Boolean(shouldRequestWriteAccess));
|
||||
}
|
||||
}, [shouldRequestWriteAccess, domain]);
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
isOpen={Boolean(urlAuth?.url)}
|
||||
onClose={handleDismiss}
|
||||
title={lang('OpenUrlTitle')}
|
||||
confirmLabel={lang('OpenUrlTitle')}
|
||||
confirmHandler={handleOpen}
|
||||
>
|
||||
{renderText(lang('OpenUrlAlert2', currentAuth?.url), ['links'])}
|
||||
{domain && (
|
||||
<Checkbox
|
||||
checked={isLoginChecked}
|
||||
label={(
|
||||
<>
|
||||
{renderText(
|
||||
lang('Conversation.OpenBotLinkLogin', [domain, getUserFullName(currentUser)]),
|
||||
['simple_markdown'],
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
onCheck={handleLoginChecked}
|
||||
className={styles.checkbox}
|
||||
/>
|
||||
)}
|
||||
{shouldRequestWriteAccess && (
|
||||
<Checkbox
|
||||
checked={isWriteAccessChecked}
|
||||
label={(
|
||||
<>
|
||||
{renderText(
|
||||
lang('Conversation.OpenBotLinkAllowMessages', getUserFullName(bot)),
|
||||
['simple_markdown'],
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
onCheck={setWriteAccessChecked}
|
||||
disabled={!isLoginChecked}
|
||||
className={styles.checkbox}
|
||||
/>
|
||||
)}
|
||||
</ConfirmDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(UrlAuthModal);
|
@ -166,6 +166,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
}, [bot, isInstalled, toggleBotInAttachMenu]);
|
||||
|
||||
const handleCloseClick = useCallback(() => {
|
||||
closeWebApp();
|
||||
}, [closeWebApp]);
|
||||
|
||||
const openBotChat = useCallback(() => {
|
||||
openChat({
|
||||
id: bot!.id,
|
||||
@ -197,7 +201,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
ariaLabel={lang('Close')}
|
||||
onClick={closeWebApp}
|
||||
onClick={handleCloseClick}
|
||||
>
|
||||
<i className="icon-close" />
|
||||
</Button>
|
||||
@ -219,7 +223,9 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
}, [lang, closeWebApp, bot, MoreMenuButton, handleRefreshClick, isInstalled, handleToggleClick, chat, openBotChat]);
|
||||
}, [
|
||||
lang, handleCloseClick, bot, MoreMenuButton, chat, openBotChat, handleRefreshClick, isInstalled, handleToggleClick,
|
||||
]);
|
||||
|
||||
const prevMainButtonColor = usePrevious(mainButton?.color, true);
|
||||
const prevMainButtonTextColor = usePrevious(mainButton?.textColor, true);
|
||||
|
@ -4,8 +4,9 @@
|
||||
max-width: var(--max-width);
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
display: grid;
|
||||
grid-auto-columns: minmax(0, 1fr);
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
|
||||
.Button {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ApiKeyboardButton, ApiMessage } from '../../../api/types';
|
||||
|
||||
import { RE_TME_LINK } from '../../../config';
|
||||
@ -18,6 +18,30 @@ type OwnProps = {
|
||||
|
||||
const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
||||
const lang = useLang();
|
||||
|
||||
const renderIcon = (button: ApiKeyboardButton) => {
|
||||
const { type } = button;
|
||||
switch (type) {
|
||||
case 'url': {
|
||||
if (!RE_TME_LINK.test(button.url)) {
|
||||
return <i className="icon-arrow-right" />;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'urlAuth':
|
||||
return <i className="icon-arrow-right" />;
|
||||
case 'buy':
|
||||
case 'receipt':
|
||||
return <i className="icon-cart" />;
|
||||
case 'switchBotInline':
|
||||
return <i className="icon-share-filled" />;
|
||||
case 'webView':
|
||||
case 'simpleWebView':
|
||||
return <i className="icon-webapp" />;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="InlineButtons">
|
||||
{message.inlineButtons!.map((row) => (
|
||||
@ -31,10 +55,7 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
||||
onClick={() => onClick({ messageId: message.id, button })}
|
||||
>
|
||||
<span className="inline-button-text">{renderText(lang(button.text))}</span>
|
||||
{['buy', 'receipt'].includes(button.type) && <i className="icon-card" />}
|
||||
{button.type === 'url' && !RE_TME_LINK.test(button.url) && <i className="icon-arrow-right" />}
|
||||
{button.type === 'switchBotInline' && <i className="icon-share-filled" />}
|
||||
{['webView', 'simpleWebView'].includes(button.type) && <i className="icon-webapp" />}
|
||||
{renderIcon(button)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
@ -108,7 +108,7 @@ const Location: FC<OwnProps> = ({
|
||||
|
||||
const handleClick = () => {
|
||||
const url = prepareMapUrl(point.lat, point.long, zoom);
|
||||
window.open(url, '_blank')?.focus();
|
||||
window.open(url, '_blank', 'noopener')?.focus();
|
||||
};
|
||||
|
||||
const updateCountdown = useCallback((countdownEl: HTMLDivElement) => {
|
||||
|
@ -23,6 +23,7 @@ type OwnProps = {
|
||||
blocking?: boolean;
|
||||
isLoading?: boolean;
|
||||
withCheckedCallback?: boolean;
|
||||
className?: string;
|
||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
onCheck?: (isChecked: boolean) => void;
|
||||
};
|
||||
@ -39,6 +40,7 @@ const Checkbox: FC<OwnProps> = ({
|
||||
round,
|
||||
blocking,
|
||||
isLoading,
|
||||
className,
|
||||
onChange,
|
||||
onCheck,
|
||||
}) => {
|
||||
@ -53,16 +55,17 @@ const Checkbox: FC<OwnProps> = ({
|
||||
}
|
||||
}, [onChange, onCheck]);
|
||||
|
||||
const className = buildClassName(
|
||||
const labelClassName = buildClassName(
|
||||
'Checkbox',
|
||||
disabled && 'disabled',
|
||||
round && 'round',
|
||||
isLoading && 'loading',
|
||||
blocking && 'blocking',
|
||||
className,
|
||||
);
|
||||
|
||||
return (
|
||||
<label className={className} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<label className={labelClassName} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
@ -74,7 +77,7 @@ const Checkbox: FC<OwnProps> = ({
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className="Checkbox-main">
|
||||
<span className="label" dir="auto">{renderText(label)}</span>
|
||||
<span className="label" dir="auto">{typeof label === 'string' ? renderText(label) : label}</span>
|
||||
{subLabel && <span className="subLabel" dir="auto">{renderText(subLabel)}</span>}
|
||||
</div>
|
||||
{isLoading && <Spinner />}
|
||||
|
@ -170,3 +170,56 @@ addActionHandler('changeSessionTtl', async (global, actions, payload) => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('loadWebAuthorizations', async () => {
|
||||
const result = await callApi('fetchWebAuthorizations');
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
setGlobal({
|
||||
...getGlobal(),
|
||||
activeWebSessions: {
|
||||
byHash: result,
|
||||
orderedHashes: Object.keys(result),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('terminateWebAuthorization', async (global, actions, payload) => {
|
||||
const { hash } = payload!;
|
||||
|
||||
const result = await callApi('terminateWebAuthorization', hash);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
const { [hash]: removedSessions, ...newSessions } = global.activeWebSessions.byHash;
|
||||
|
||||
setGlobal({
|
||||
...global,
|
||||
activeWebSessions: {
|
||||
byHash: newSessions,
|
||||
orderedHashes: global.activeWebSessions.orderedHashes.filter((el) => el !== hash),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('terminateAllWebAuthorizations', async (global) => {
|
||||
const result = await callApi('terminateAllWebAuthorizations');
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
setGlobal({
|
||||
...global,
|
||||
activeWebSessions: {
|
||||
byHash: {},
|
||||
orderedHashes: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -2,12 +2,11 @@ import {
|
||||
addActionHandler, getActions, getGlobal, setGlobal,
|
||||
} from '../../index';
|
||||
|
||||
import type { ApiChat, ApiContact, ApiUser } from '../../../api/types';
|
||||
import type {
|
||||
ApiChat, ApiContact, ApiUrlAuthResult, ApiUser,
|
||||
} from '../../../api/types';
|
||||
import type { InlineBotSettings } from '../../../types';
|
||||
|
||||
import {
|
||||
RE_TG_LINK, RE_TME_LINK,
|
||||
} from '../../../config';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import {
|
||||
selectBot,
|
||||
@ -35,11 +34,7 @@ addActionHandler('clickBotInlineButton', (global, actions, payload) => {
|
||||
break;
|
||||
case 'url': {
|
||||
const { url } = button;
|
||||
if (url.match(RE_TME_LINK) || url.match(RE_TG_LINK)) {
|
||||
actions.openTelegramLink({ url });
|
||||
} else {
|
||||
actions.toggleSafeLinkModal({ url });
|
||||
}
|
||||
actions.openUrl({ url });
|
||||
break;
|
||||
}
|
||||
case 'callback': {
|
||||
@ -154,6 +149,20 @@ addActionHandler('clickBotInlineButton', (global, actions, payload) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'urlAuth': {
|
||||
const { url } = button;
|
||||
const chat = selectCurrentChat(global);
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
actions.requestBotUrlAuth({
|
||||
chatId: chat.id,
|
||||
messageId,
|
||||
buttonId: button.buttonId,
|
||||
url,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -615,6 +624,117 @@ addActionHandler('closeBotAttachRequestModal', (global) => {
|
||||
};
|
||||
});
|
||||
|
||||
addActionHandler('requestBotUrlAuth', async (global, actions, payload) => {
|
||||
const {
|
||||
chatId, buttonId, messageId, url,
|
||||
} = payload;
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await callApi('requestBotUrlAuth', {
|
||||
chat,
|
||||
buttonId,
|
||||
messageId,
|
||||
});
|
||||
|
||||
if (!result) return;
|
||||
global = getGlobal();
|
||||
setGlobal({
|
||||
...global,
|
||||
urlAuth: {
|
||||
url,
|
||||
button: {
|
||||
buttonId,
|
||||
messageId,
|
||||
chatId: chat.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
handleUrlAuthResult(url, result);
|
||||
});
|
||||
|
||||
addActionHandler('acceptBotUrlAuth', async (global, actions, payload) => {
|
||||
const { isWriteAllowed } = payload;
|
||||
if (!global.urlAuth?.button) return;
|
||||
const {
|
||||
button, url,
|
||||
} = global.urlAuth;
|
||||
const { chatId, messageId, buttonId } = button;
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await callApi('acceptBotUrlAuth', {
|
||||
chat,
|
||||
messageId,
|
||||
buttonId,
|
||||
isWriteAllowed,
|
||||
});
|
||||
if (!result) return;
|
||||
handleUrlAuthResult(url, result);
|
||||
});
|
||||
|
||||
addActionHandler('requestLinkUrlAuth', async (global, actions, payload) => {
|
||||
const { url } = payload;
|
||||
|
||||
const result = await callApi('requestLinkUrlAuth', { url });
|
||||
if (!result) return;
|
||||
global = getGlobal();
|
||||
setGlobal({
|
||||
...global,
|
||||
urlAuth: {
|
||||
url,
|
||||
},
|
||||
});
|
||||
handleUrlAuthResult(url, result);
|
||||
});
|
||||
|
||||
addActionHandler('acceptLinkUrlAuth', async (global, actions, payload) => {
|
||||
const { isWriteAllowed } = payload;
|
||||
if (!global.urlAuth?.url) return;
|
||||
const { url } = global.urlAuth;
|
||||
|
||||
const result = await callApi('acceptLinkUrlAuth', { url, isWriteAllowed });
|
||||
if (!result) return;
|
||||
handleUrlAuthResult(url, result);
|
||||
});
|
||||
|
||||
addActionHandler('closeUrlAuthModal', (global) => {
|
||||
return {
|
||||
...global,
|
||||
urlAuth: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
function handleUrlAuthResult(url: string, result: ApiUrlAuthResult) {
|
||||
if (result.type === 'request') {
|
||||
const global = getGlobal();
|
||||
if (!global.urlAuth) return;
|
||||
const { domain, bot, shouldRequestWriteAccess } = result;
|
||||
setGlobal({
|
||||
...global,
|
||||
urlAuth: {
|
||||
...global.urlAuth,
|
||||
request: {
|
||||
domain,
|
||||
botId: bot.id,
|
||||
shouldRequestWriteAccess,
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const siteUrl = result.type === 'accepted' ? result.url : url;
|
||||
window.open(siteUrl, '_blank', 'noopener');
|
||||
getActions().closeUrlAuthModal();
|
||||
}
|
||||
|
||||
async function searchInlineBot({
|
||||
username,
|
||||
inlineBotData,
|
||||
@ -691,7 +811,7 @@ let gameePopups: PopupManager | undefined;
|
||||
|
||||
async function answerCallbackButton(chat: ApiChat, messageId: number, data?: string, isGame = false) {
|
||||
const {
|
||||
showDialog, showNotification, toggleSafeLinkModal, openGame,
|
||||
showDialog, showNotification, openUrl, openGame,
|
||||
} = getActions();
|
||||
|
||||
if (isGame) {
|
||||
@ -731,7 +851,7 @@ async function answerCallbackButton(chat: ApiChat, messageId: number, data?: str
|
||||
openGame({ url, chatId: chat.id, messageId });
|
||||
}
|
||||
} else {
|
||||
toggleSafeLinkModal({ url });
|
||||
openUrl({ url });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import { LoadMoreDirection } from '../../../types';
|
||||
import {
|
||||
MAX_MEDIA_FILES_FOR_ALBUM,
|
||||
MESSAGE_LIST_SLICE,
|
||||
RE_TG_LINK,
|
||||
RE_TME_LINK,
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
} from '../../../config';
|
||||
import { IS_IOS } from '../../../util/environment';
|
||||
@ -70,6 +72,9 @@ import {
|
||||
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers';
|
||||
import { getMessageOriginalId, isServiceNotificationMessage } from '../../helpers';
|
||||
import { getTranslation } from '../../../util/langProvider';
|
||||
import { ensureProtocol } from '../../../util/ensureProtocol';
|
||||
|
||||
const AUTOLOGIN_TOKEN_KEY = 'autologin_token';
|
||||
|
||||
const uploadProgressCallbacks = new Map<number, ApiOnProgress>();
|
||||
|
||||
@ -1157,6 +1162,38 @@ addActionHandler('readAllMentions', (global) => {
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('openUrl', (global, actions, payload) => {
|
||||
const { url, shouldSkipModal } = payload;
|
||||
const urlWithProtocol = ensureProtocol(url)!;
|
||||
|
||||
if (urlWithProtocol.match(RE_TME_LINK) || urlWithProtocol.match(RE_TG_LINK)) {
|
||||
actions.openTelegramLink({ url });
|
||||
return;
|
||||
}
|
||||
|
||||
const { appConfig } = global;
|
||||
if (appConfig) {
|
||||
const parsedUrl = new URL(urlWithProtocol);
|
||||
|
||||
if (appConfig.autologinDomains.includes(parsedUrl.hostname)) {
|
||||
parsedUrl.searchParams.set(AUTOLOGIN_TOKEN_KEY, appConfig.autologinToken);
|
||||
window.open(parsedUrl.href, '_blank', 'noopener');
|
||||
return;
|
||||
}
|
||||
|
||||
if (appConfig.urlAuthDomains.includes(parsedUrl.hostname)) {
|
||||
actions.requestLinkUrlAuth({ url });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldSkipModal) {
|
||||
actions.toggleSafeLinkModal({ url: urlWithProtocol });
|
||||
} else {
|
||||
window.open(urlWithProtocol, '_blank', 'noopener');
|
||||
}
|
||||
});
|
||||
|
||||
function countSortedIds(ids: number[], from: number, to: number) {
|
||||
let count = 0;
|
||||
|
||||
|
@ -252,6 +252,13 @@ function migrateCache(cached: GlobalState, initialState: GlobalState) {
|
||||
orderedHashes: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!cached.activeWebSessions) {
|
||||
cached.activeWebSessions = {
|
||||
byHash: {},
|
||||
orderedHashes: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function updateCache() {
|
||||
|
@ -143,6 +143,11 @@ export const INITIAL_STATE: GlobalState = {
|
||||
orderedHashes: [],
|
||||
},
|
||||
|
||||
activeWebSessions: {
|
||||
byHash: {},
|
||||
orderedHashes: [],
|
||||
},
|
||||
|
||||
settings: {
|
||||
byKey: {
|
||||
theme: 'light',
|
||||
|
@ -37,6 +37,7 @@ import type {
|
||||
ApiThemeParameters,
|
||||
ApiAttachMenuBot,
|
||||
ApiPhoneCall,
|
||||
ApiWebSession,
|
||||
} from '../api/types';
|
||||
import type {
|
||||
FocusDirection,
|
||||
@ -490,6 +491,11 @@ export type GlobalState = {
|
||||
ttlDays?: number;
|
||||
};
|
||||
|
||||
activeWebSessions: {
|
||||
byHash: Record<string, ApiWebSession>;
|
||||
orderedHashes: string[];
|
||||
};
|
||||
|
||||
settings: {
|
||||
byKey: ISettings;
|
||||
loadedWallpapers?: ApiWallpaper[];
|
||||
@ -589,6 +595,20 @@ export type GlobalState = {
|
||||
hash?: string;
|
||||
bots: Record<string, ApiAttachMenuBot>;
|
||||
};
|
||||
|
||||
urlAuth?: {
|
||||
button?: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
buttonId: number;
|
||||
};
|
||||
request?: {
|
||||
domain: string;
|
||||
botId: string;
|
||||
shouldRequestWriteAccess?: boolean;
|
||||
};
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type CallSound = (
|
||||
@ -637,7 +657,7 @@ export interface ActionPayloads {
|
||||
text: string;
|
||||
};
|
||||
|
||||
resetOpenChatWithText: {};
|
||||
resetOpenChatWithText: never;
|
||||
|
||||
// Messages
|
||||
setEditingDraft: {
|
||||
@ -657,10 +677,10 @@ export interface ActionPayloads {
|
||||
animateUnreadReaction: {
|
||||
messageIds: number[];
|
||||
};
|
||||
focusNextReaction: {};
|
||||
focusNextMention: {};
|
||||
readAllReactions: {};
|
||||
readAllMentions: {};
|
||||
focusNextReaction: never;
|
||||
focusNextMention: never;
|
||||
readAllReactions: never;
|
||||
readAllMentions: never;
|
||||
markMentionsRead: {
|
||||
messageIds: number[];
|
||||
};
|
||||
@ -677,7 +697,7 @@ export interface ActionPayloads {
|
||||
playbackRate?: number;
|
||||
isMuted?: boolean;
|
||||
};
|
||||
closeMediaViewer: {};
|
||||
closeMediaViewer: never;
|
||||
setMediaViewerVolume: {
|
||||
volume: number;
|
||||
};
|
||||
@ -697,7 +717,7 @@ export interface ActionPayloads {
|
||||
playbackRate?: number;
|
||||
isMuted?: boolean;
|
||||
};
|
||||
closeAudioPlayer: {};
|
||||
closeAudioPlayer: never;
|
||||
setAudioPlayerVolume: {
|
||||
volume: number;
|
||||
};
|
||||
@ -712,7 +732,7 @@ export interface ActionPayloads {
|
||||
};
|
||||
|
||||
// Downloads
|
||||
downloadSelectedMessages: {};
|
||||
downloadSelectedMessages: never;
|
||||
downloadMessageMedia: {
|
||||
message: ApiMessage;
|
||||
};
|
||||
@ -787,14 +807,14 @@ export interface ActionPayloads {
|
||||
isSamePeer?: boolean;
|
||||
};
|
||||
|
||||
resetSwitchBotInline: {};
|
||||
resetSwitchBotInline: never;
|
||||
|
||||
openGame: {
|
||||
url: string;
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
};
|
||||
closeGame: {};
|
||||
closeGame: never;
|
||||
|
||||
requestWebView: {
|
||||
url?: string;
|
||||
@ -819,9 +839,9 @@ export interface ActionPayloads {
|
||||
buttonText: string;
|
||||
theme?: ApiThemeParameters;
|
||||
};
|
||||
closeWebApp: {};
|
||||
closeWebApp: never;
|
||||
|
||||
cancelBotTrustRequest: {};
|
||||
cancelBotTrustRequest: never;
|
||||
markBotTrusted: {
|
||||
botId: string;
|
||||
};
|
||||
@ -852,11 +872,52 @@ export interface ActionPayloads {
|
||||
startParam?: string;
|
||||
};
|
||||
|
||||
requestBotUrlAuth: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
buttonId: number;
|
||||
url: string;
|
||||
};
|
||||
|
||||
acceptBotUrlAuth: {
|
||||
isWriteAllowed?: boolean;
|
||||
};
|
||||
|
||||
requestLinkUrlAuth: {
|
||||
url: string;
|
||||
};
|
||||
|
||||
acceptLinkUrlAuth: {
|
||||
isWriteAllowed?: boolean;
|
||||
};
|
||||
|
||||
// Settings
|
||||
loadAuthorizations: never;
|
||||
terminateAuthorization: {
|
||||
hash: string;
|
||||
};
|
||||
terminateAllAuthorizations: never;
|
||||
|
||||
loadWebAuthorizations: never;
|
||||
terminateWebAuthorization: {
|
||||
hash: string;
|
||||
};
|
||||
terminateAllWebAuthorizations: never;
|
||||
|
||||
// Misc
|
||||
openPollModal: {
|
||||
isQuiz?: boolean;
|
||||
};
|
||||
closePollModal: {};
|
||||
closePollModal: never;
|
||||
|
||||
openUrl: {
|
||||
url: string;
|
||||
shouldSkipModal?: boolean;
|
||||
};
|
||||
toggleSafeLinkModal: {
|
||||
url?: string;
|
||||
};
|
||||
closeUrlAuthModal: never;
|
||||
|
||||
// Calls
|
||||
requestCall: {
|
||||
@ -864,17 +925,17 @@ export interface ActionPayloads {
|
||||
isVideo?: boolean;
|
||||
};
|
||||
sendSignalingData: P2pMessage;
|
||||
hangUp: {};
|
||||
acceptCall: {};
|
||||
hangUp: never;
|
||||
acceptCall: never;
|
||||
setCallRating: {
|
||||
rating: number;
|
||||
comment: string;
|
||||
};
|
||||
closeCallRatingModal: {};
|
||||
closeCallRatingModal: never;
|
||||
playGroupCallSound: {
|
||||
sound: CallSound;
|
||||
};
|
||||
connectToActivePhoneCall: {};
|
||||
connectToActivePhoneCall: never;
|
||||
|
||||
// Passcode
|
||||
setPasscode: { passcode: string };
|
||||
@ -959,7 +1020,6 @@ export type NonTypedActionNames = (
|
||||
'setSettingOption' | 'loadPasswordInfo' | 'clearTwoFaError' |
|
||||
'updatePassword' | 'updateRecoveryEmail' | 'clearPassword' | 'provideTwoFaEmailCode' | 'checkPassword' |
|
||||
'loadBlockedContacts' | 'blockContact' | 'unblockContact' |
|
||||
'loadAuthorizations' | 'terminateAuthorization' | 'terminateAllAuthorizations' |
|
||||
'loadNotificationSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings' |
|
||||
'updateWebNotificationSettings' | 'loadLanguages' | 'loadPrivacySettings' | 'setPrivacyVisibility' |
|
||||
'setPrivacySettings' | 'loadNotificationExceptions' | 'setThemeSettings' | 'updateIsOnline' |
|
||||
@ -987,7 +1047,7 @@ export type NonTypedActionNames = (
|
||||
'loadMoreGroupCallParticipants' | 'connectToActiveGroupCall' |
|
||||
// stats
|
||||
'loadStatistics' | 'loadMessageStatistics' | 'loadStatisticsAsyncGraph'
|
||||
);
|
||||
);
|
||||
|
||||
const typed = typify<GlobalState, ActionPayloads, NonTypedActionNames>();
|
||||
export type GlobalActions = ReturnType<typeof typed.getActions>;
|
||||
|
@ -1023,6 +1023,9 @@ account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_setti
|
||||
account.sendConfirmPhoneCode#1b3faa88 hash:string settings:CodeSettings = auth.SentCode;
|
||||
account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
|
||||
account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword;
|
||||
account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
|
||||
account.resetWebAuthorization#2d01b9ef hash:long = Bool;
|
||||
account.resetWebAuthorizations#682d2594 = Bool;
|
||||
account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode;
|
||||
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
||||
account.getContactSignUpNotification#9f07c728 = Bool;
|
||||
@ -1115,6 +1118,8 @@ messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
|
||||
messages.editChatAbout#def60797 peer:InputPeer about:string = Bool;
|
||||
messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates;
|
||||
messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference;
|
||||
messages.requestUrlAuth#198fb446 flags:# peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult;
|
||||
messages.acceptUrlAuth#b12c7125 flags:# write_allowed:flags.0?true peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult;
|
||||
messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool;
|
||||
messages.getScheduledHistory#f516760b peer:InputPeer hash:long = messages.Messages;
|
||||
messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
|
||||
|
@ -35,6 +35,9 @@
|
||||
"account.sendConfirmPhoneCode",
|
||||
"account.confirmPhone",
|
||||
"account.getTmpPassword",
|
||||
"account.getWebAuthorizations",
|
||||
"account.resetWebAuthorization",
|
||||
"account.resetWebAuthorizations",
|
||||
"account.sendVerifyPhoneCode",
|
||||
"account.confirmPasswordEmail",
|
||||
"account.getContactSignUpNotification",
|
||||
@ -131,6 +134,8 @@
|
||||
"messages.editChatAbout",
|
||||
"messages.editChatDefaultBannedRights",
|
||||
"messages.getEmojiKeywordsDifference",
|
||||
"messages.requestUrlAuth",
|
||||
"messages.acceptUrlAuth",
|
||||
"messages.hidePeerSettingsBar",
|
||||
"messages.getScheduledHistory",
|
||||
"messages.sendScheduledMessages",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -51,6 +51,9 @@
|
||||
.icon-volume-3:before {
|
||||
content: "\e991";
|
||||
}
|
||||
.icon-web:before {
|
||||
content: "\e99b";
|
||||
}
|
||||
.icon-key:before {
|
||||
content: "\e99a";
|
||||
}
|
||||
|
@ -214,6 +214,7 @@ export enum SettingsScreens {
|
||||
TwoFaRecoveryEmailCode,
|
||||
TwoFaCongratulations,
|
||||
QuickReaction,
|
||||
ActiveWebsites,
|
||||
PasscodeDisabled,
|
||||
PasscodeNewPasscode,
|
||||
PasscodeNewPasscodeConfirm,
|
||||
|
Loading…
Reference in New Issue
Block a user