Implement Seamless Login (#1865)
This commit is contained in:
parent
52fa0c8fca
commit
b39ae6f0a1
@ -17,6 +17,9 @@ type GramJsAppConfig = {
|
|||||||
reactions_uniq_max: number;
|
reactions_uniq_max: number;
|
||||||
chat_read_mark_size_threshold: number;
|
chat_read_mark_size_threshold: number;
|
||||||
chat_read_mark_expire_period: number;
|
chat_read_mark_expire_period: number;
|
||||||
|
autologin_domains: string[];
|
||||||
|
autologin_token: string;
|
||||||
|
url_auth_domains: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
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;
|
const appConfig = buildJson(json) as GramJsAppConfig;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -46,5 +49,8 @@ export function buildApiConfig(json: GramJs.TypeJSONValue): ApiAppConfig {
|
|||||||
defaultReaction: appConfig.reactions_default,
|
defaultReaction: appConfig.reactions_default,
|
||||||
seenByMaxChatMembers: appConfig.chat_read_mark_size_threshold,
|
seenByMaxChatMembers: appConfig.chat_read_mark_size_threshold,
|
||||||
seenByExpiresAt: appConfig.chat_read_mark_expire_period,
|
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 {
|
return {
|
||||||
type: 'unsupported',
|
type: 'unsupported',
|
||||||
text,
|
text,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiCountry, ApiSession, ApiWallpaper,
|
ApiCountry, ApiSession, ApiUrlAuthResult, ApiWallpaper, ApiWebSession,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import type { ApiPrivacySettings, ApiPrivacyKey, PrivacyVisibility } from '../../../types';
|
import type { ApiPrivacySettings, ApiPrivacyKey, PrivacyVisibility } from '../../../types';
|
||||||
|
|
||||||
@ -9,6 +9,8 @@ import { buildApiDocument } from './messages';
|
|||||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import { buildApiUser } from './users';
|
||||||
|
import { addUserToLocalDb } from '../helpers';
|
||||||
|
|
||||||
export function buildApiWallpaper(wallpaper: GramJs.TypeWallPaper): ApiWallpaper | undefined {
|
export function buildApiWallpaper(wallpaper: GramJs.TypeWallPaper): ApiWallpaper | undefined {
|
||||||
if (wallpaper instanceof GramJs.WallPaperNoFile) {
|
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 {
|
export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | undefined {
|
||||||
switch (key.className) {
|
switch (key.className) {
|
||||||
case 'PrivacyKeyPhoneNumber':
|
case 'PrivacyKeyPhoneNumber':
|
||||||
@ -176,3 +188,34 @@ export function buildJson(json: GramJs.TypeJSONValue): any {
|
|||||||
return acc;
|
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 BigInt from 'big-integer';
|
||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
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 localDb from '../localDb';
|
||||||
import { invokeRequest } from './client';
|
import { invokeRequest } from './client';
|
||||||
@ -14,8 +16,12 @@ import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
|||||||
import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
|
import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
|
||||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
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({
|
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[]) {
|
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
||||||
return results.map((result) => {
|
return results.map((result) => {
|
||||||
if (result instanceof GramJs.BotInlineMediaResult) {
|
if (result instanceof GramJs.BotInlineMediaResult) {
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export {
|
|||||||
updateProfile, checkUsername, updateUsername, fetchBlockedContacts, blockContact, unblockContact,
|
updateProfile, checkUsername, updateUsername, fetchBlockedContacts, blockContact, unblockContact,
|
||||||
updateProfilePhoto, uploadProfilePhoto, fetchWallpapers, uploadWallpaper,
|
updateProfilePhoto, uploadProfilePhoto, fetchWallpapers, uploadWallpaper,
|
||||||
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
|
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
|
||||||
|
fetchWebAuthorizations, terminateWebAuthorization, terminateAllWebAuthorizations,
|
||||||
fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings,
|
fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings,
|
||||||
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings, registerDevice, unregisterDevice,
|
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings, registerDevice, unregisterDevice,
|
||||||
updateIsOnline, fetchContentSettings, updateContentSettings, fetchLangStrings, fetchCountryList, fetchAppConfig,
|
updateIsOnline, fetchContentSettings, updateContentSettings, fetchLangStrings, fetchCountryList, fetchAppConfig,
|
||||||
@ -66,6 +67,7 @@ export {
|
|||||||
export {
|
export {
|
||||||
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
||||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachMenuBots, toggleBotInAttachMenu,
|
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachMenuBots, toggleBotInAttachMenu,
|
||||||
|
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth,
|
||||||
} from './bots';
|
} from './bots';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -86,7 +88,7 @@ export {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
fetchChannelStatistics, fetchGroupStatistics, fetchMessageStatistics,
|
fetchChannelStatistics, fetchGroupStatistics, fetchMessageStatistics,
|
||||||
fetchMessagePublicForwards, fetchStatisticsAsyncGraph,
|
fetchMessagePublicForwards, fetchStatisticsAsyncGraph,
|
||||||
} from './statistics';
|
} from './statistics';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -19,20 +19,21 @@ import {
|
|||||||
buildApiNotifyException,
|
buildApiNotifyException,
|
||||||
buildApiSession,
|
buildApiSession,
|
||||||
buildApiWallpaper,
|
buildApiWallpaper,
|
||||||
|
buildApiWebSession,
|
||||||
buildPrivacyRules,
|
buildPrivacyRules,
|
||||||
} from '../apiBuilders/misc';
|
} from '../apiBuilders/misc';
|
||||||
|
|
||||||
import { buildApiUser } from '../apiBuilders/users';
|
import { buildApiUser } from '../apiBuilders/users';
|
||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
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 { buildInputEntity, buildInputPeer, buildInputPrivacyKey } from '../gramjsBuilders';
|
||||||
import { getClient, invokeRequest, uploadFile } from './client';
|
import { getClient, invokeRequest, uploadFile } from './client';
|
||||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
|
||||||
import localDb from '../localDb';
|
|
||||||
import { buildApiConfig } from '../apiBuilders/appConfig';
|
|
||||||
import { addEntitiesWithPhotosToLocalDb } from '../helpers';
|
import { addEntitiesWithPhotosToLocalDb } from '../helpers';
|
||||||
|
import localDb from '../localDb';
|
||||||
|
|
||||||
const MAX_INT_32 = 2 ** 31 - 1;
|
const MAX_INT_32 = 2 ** 31 - 1;
|
||||||
const BETA_LANG_CODES = ['ar', 'fa', 'id', 'ko', 'uz', 'en'];
|
const BETA_LANG_CODES = ['ar', 'fa', 'id', 'ko', 'uz', 'en'];
|
||||||
@ -175,6 +176,23 @@ export function terminateAllAuthorizations() {
|
|||||||
return invokeRequest(new GramJs.auth.ResetAuthorizations());
|
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({
|
export async function fetchNotificationExceptions({
|
||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
}: { serverTimeOffset: number }) {
|
}: { serverTimeOffset: number }) {
|
||||||
@ -451,7 +469,7 @@ export async function fetchAppConfig(): Promise<ApiAppConfig | undefined> {
|
|||||||
const result = await invokeRequest(new GramJs.help.GetAppConfig());
|
const result = await invokeRequest(new GramJs.help.GetAppConfig());
|
||||||
if (!result) return undefined;
|
if (!result) return undefined;
|
||||||
|
|
||||||
return buildApiConfig(result);
|
return buildAppConfig(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLocalDb(
|
function updateLocalDb(
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { init as initClient } from './methods/client';
|
|||||||
import { init as initStickers } from './methods/symbols';
|
import { init as initStickers } from './methods/symbols';
|
||||||
import { init as initManagement } from './methods/management';
|
import { init as initManagement } from './methods/management';
|
||||||
import { init as initTwoFaSettings } from './methods/twoFaSettings';
|
import { init as initTwoFaSettings } from './methods/twoFaSettings';
|
||||||
|
import { init as initBots } from './methods/bots';
|
||||||
import { init as initCalls } from './methods/calls';
|
import { init as initCalls } from './methods/calls';
|
||||||
import { init as initPayments } from './methods/payments';
|
import { init as initPayments } from './methods/payments';
|
||||||
import * as methods from './methods';
|
import * as methods from './methods';
|
||||||
@ -34,6 +35,7 @@ export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArg
|
|||||||
initStickers(handleUpdate);
|
initStickers(handleUpdate);
|
||||||
initManagement(handleUpdate);
|
initManagement(handleUpdate);
|
||||||
initTwoFaSettings(handleUpdate);
|
initTwoFaSettings(handleUpdate);
|
||||||
|
initBots(handleUpdate);
|
||||||
initCalls(handleUpdate);
|
initCalls(handleUpdate);
|
||||||
initPayments(handleUpdate);
|
initPayments(handleUpdate);
|
||||||
|
|
||||||
|
|||||||
@ -438,6 +438,13 @@ interface ApiKeyboardButtonUserProfile {
|
|||||||
userId: string;
|
userId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ApiKeyboardButtonUrlAuth {
|
||||||
|
type: 'urlAuth';
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
buttonId: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type ApiKeyboardButton = (
|
export type ApiKeyboardButton = (
|
||||||
ApiKeyboardButtonSimple
|
ApiKeyboardButtonSimple
|
||||||
| ApiKeyboardButtonReceipt
|
| ApiKeyboardButtonReceipt
|
||||||
@ -448,6 +455,7 @@ export type ApiKeyboardButton = (
|
|||||||
| ApiKeyboardButtonUserProfile
|
| ApiKeyboardButtonUserProfile
|
||||||
| ApiKeyboardButtonWebView
|
| ApiKeyboardButtonWebView
|
||||||
| ApiKeyboardButtonSimpleWebView
|
| ApiKeyboardButtonSimpleWebView
|
||||||
|
| ApiKeyboardButtonUrlAuth
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { ApiDocument, ApiPhoto } from './messages';
|
import type { ApiDocument, ApiPhoto } from './messages';
|
||||||
|
import type { ApiUser } from './users';
|
||||||
|
|
||||||
export interface ApiInitialArgs {
|
export interface ApiInitialArgs {
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
@ -65,6 +66,18 @@ export interface ApiSession {
|
|||||||
areSecretChatsEnabled: boolean;
|
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 {
|
export interface ApiSessionData {
|
||||||
mainDcId: number;
|
mainDcId: number;
|
||||||
keys: Record<number, string | number[]>;
|
keys: Record<number, string | number[]>;
|
||||||
@ -145,6 +158,9 @@ export interface ApiAppConfig {
|
|||||||
defaultReaction: string;
|
defaultReaction: string;
|
||||||
seenByMaxChatMembers: number;
|
seenByMaxChatMembers: number;
|
||||||
seenByExpiresAt: number;
|
seenByExpiresAt: number;
|
||||||
|
autologinDomains: string[];
|
||||||
|
autologinToken: string;
|
||||||
|
urlAuthDomains: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GramJsEmojiInteraction {
|
export interface GramJsEmojiInteraction {
|
||||||
@ -158,3 +174,21 @@ export interface GramJsEmojiInteraction {
|
|||||||
export interface ApiEmojiInteraction {
|
export interface ApiEmojiInteraction {
|
||||||
timestamps: number[];
|
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 { ApiChat } from './chats';
|
||||||
import type { ApiMessage, ApiPhoto } from './messages';
|
import type { ApiMessage } from './messages';
|
||||||
|
|
||||||
export interface ApiChannelStatistics {
|
export interface ApiChannelStatistics {
|
||||||
growthGraph?: StatisticsGraph | string;
|
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 Dialogs } from '../components/main/Dialogs';
|
||||||
export { default as Notifications } from '../components/main/Notifications';
|
export { default as Notifications } from '../components/main/Notifications';
|
||||||
export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
|
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 HistoryCalendar } from '../components/main/HistoryCalendar';
|
||||||
export { default as NewContactModal } from '../components/main/NewContactModal';
|
export { default as NewContactModal } from '../components/main/NewContactModal';
|
||||||
export { default as WebAppModal } from '../components/main/WebAppModal';
|
export { default as WebAppModal } from '../components/main/WebAppModal';
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import React, {
|
||||||
import React, { memo, useRef, useState } from '../../../lib/teact/teact';
|
memo, useRef, useState, useCallback,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
@ -41,6 +44,10 @@ const RatePhoneCallModal: FC<OwnProps> = ({
|
|||||||
return () => setRating(rating === index ? undefined : index);
|
return () => setRating(rating === index ? undefined : index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCancelClick = useCallback(() => {
|
||||||
|
closeCallRatingModal();
|
||||||
|
}, [closeCallRatingModal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title={lang('lng_call_rate_label')} className="narrow" onClose={closeCallRatingModal} isOpen={isOpen}>
|
<Modal title={lang('lng_call_rate_label')} className="narrow" onClose={closeCallRatingModal} isOpen={isOpen}>
|
||||||
<div className={styles.stars}>
|
<div className={styles.stars}>
|
||||||
@ -68,7 +75,7 @@ const RatePhoneCallModal: FC<OwnProps> = ({
|
|||||||
<Button className="confirm-dialog-button" isText onClick={handleSend}>
|
<Button className="confirm-dialog-button" isText onClick={handleSend}>
|
||||||
{lang('Send')}
|
{lang('Send')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="confirm-dialog-button" isText onClick={closeCallRatingModal}>{lang('Cancel')}</Button>
|
<Button className="confirm-dialog-button" isText onClick={handleCancelClick}>{lang('Cancel')}</Button>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { getActions } from '../../global';
|
|||||||
import convertPunycode from '../../lib/punycode';
|
import convertPunycode from '../../lib/punycode';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEBUG, RE_TG_LINK, RE_TME_LINK,
|
DEBUG,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { ensureProtocol } from '../../util/ensureProtocol';
|
import { ensureProtocol } from '../../util/ensureProtocol';
|
||||||
@ -24,31 +24,18 @@ const SafeLink: FC<OwnProps> = ({
|
|||||||
children,
|
children,
|
||||||
isRtl,
|
isRtl,
|
||||||
}) => {
|
}) => {
|
||||||
const { toggleSafeLinkModal, openTelegramLink } = getActions();
|
const { openUrl } = getActions();
|
||||||
|
|
||||||
const content = children || text;
|
const content = children || text;
|
||||||
const isNotSafe = url !== content;
|
const isSafe = url === content;
|
||||||
|
|
||||||
const handleClick = useCallback((e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
const handleClick = useCallback((e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||||
if (
|
if (!url) return true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openTelegramLink({ url });
|
openUrl({ url, shouldSkipModal: isSafe });
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, [isNotSafe, openTelegramLink, toggleSafeLinkModal, url]);
|
}, [isSafe, openUrl, url]);
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -161,6 +161,7 @@ const LeftColumn: FC<StateProps> = ({
|
|||||||
case SettingsScreens.PrivacyForwarding:
|
case SettingsScreens.PrivacyForwarding:
|
||||||
case SettingsScreens.PrivacyGroupChats:
|
case SettingsScreens.PrivacyGroupChats:
|
||||||
case SettingsScreens.PrivacyBlockedUsers:
|
case SettingsScreens.PrivacyBlockedUsers:
|
||||||
|
case SettingsScreens.ActiveWebsites:
|
||||||
case SettingsScreens.TwoFaDisabled:
|
case SettingsScreens.TwoFaDisabled:
|
||||||
case SettingsScreens.TwoFaEnabled:
|
case SettingsScreens.TwoFaEnabled:
|
||||||
case SettingsScreens.TwoFaCongratulations:
|
case SettingsScreens.TwoFaCongratulations:
|
||||||
|
|||||||
@ -208,7 +208,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
}, [animationLevel, setSettingOption, theme]);
|
}, [animationLevel, setSettingOption, theme]);
|
||||||
|
|
||||||
const handleChangelogClick = useCallback(() => {
|
const handleChangelogClick = useCallback(() => {
|
||||||
window.open(BETA_CHANGELOG_URL, '_blank');
|
window.open(BETA_CHANGELOG_URL, '_blank', 'noopener');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleRuDiscussionClick = useCallback(() => {
|
const handleRuDiscussionClick = useCallback(() => {
|
||||||
|
|||||||
@ -182,6 +182,7 @@
|
|||||||
|
|
||||||
&.full-size {
|
&.full-size {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import SettingsPrivacy from './SettingsPrivacy';
|
|||||||
import SettingsLanguage from './SettingsLanguage';
|
import SettingsLanguage from './SettingsLanguage';
|
||||||
import SettingsPrivacyVisibility from './SettingsPrivacyVisibility';
|
import SettingsPrivacyVisibility from './SettingsPrivacyVisibility';
|
||||||
import SettingsActiveSessions from './SettingsActiveSessions';
|
import SettingsActiveSessions from './SettingsActiveSessions';
|
||||||
|
import SettingsActiveWebsites from './SettingsActiveWebsites';
|
||||||
import SettingsPrivacyBlockedUsers from './SettingsPrivacyBlockedUsers';
|
import SettingsPrivacyBlockedUsers from './SettingsPrivacyBlockedUsers';
|
||||||
import SettingsTwoFa from './twoFa/SettingsTwoFa';
|
import SettingsTwoFa from './twoFa/SettingsTwoFa';
|
||||||
import SettingsPrivacyVisibilityExceptionList from './SettingsPrivacyVisibilityExceptionList';
|
import SettingsPrivacyVisibilityExceptionList from './SettingsPrivacyVisibilityExceptionList';
|
||||||
@ -69,7 +70,7 @@ const FOLDERS_SCREENS = [
|
|||||||
|
|
||||||
const PRIVACY_SCREENS = [
|
const PRIVACY_SCREENS = [
|
||||||
SettingsScreens.PrivacyBlockedUsers,
|
SettingsScreens.PrivacyBlockedUsers,
|
||||||
SettingsScreens.ActiveSessions,
|
SettingsScreens.ActiveWebsites,
|
||||||
];
|
];
|
||||||
|
|
||||||
const PRIVACY_PHONE_NUMBER_SCREENS = [
|
const PRIVACY_PHONE_NUMBER_SCREENS = [
|
||||||
@ -258,6 +259,13 @@ const Settings: FC<OwnProps> = ({
|
|||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case SettingsScreens.ActiveWebsites:
|
||||||
|
return (
|
||||||
|
<SettingsActiveWebsites
|
||||||
|
isActive={isScreenActive}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case SettingsScreens.PrivacyBlockedUsers:
|
case SettingsScreens.PrivacyBlockedUsers:
|
||||||
return (
|
return (
|
||||||
<SettingsPrivacyBlockedUsers
|
<SettingsPrivacyBlockedUsers
|
||||||
|
|||||||
@ -9,7 +9,7 @@ $icons: "android", "apple", "brave", "chrome", "edge", "firefox", "linux", "oper
|
|||||||
.SettingsActiveSessions {
|
.SettingsActiveSessions {
|
||||||
.icon-device {
|
.icon-device {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height:2rem;
|
height: 2rem;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 2rem;
|
background-size: 2rem;
|
||||||
flex: 0 0 2rem;
|
flex: 0 0 2rem;
|
||||||
|
|||||||
@ -195,6 +195,7 @@ const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
|
|||||||
contextActions={[{
|
contextActions={[{
|
||||||
title: 'Terminate',
|
title: 'Terminate',
|
||||||
icon: 'stop',
|
icon: 'stop',
|
||||||
|
destructive: true,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
handleTerminateSessionClick(session.hash);
|
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:
|
case SettingsScreens.ActiveSessions:
|
||||||
return <h3>{lang('SessionsTitle')}</h3>;
|
return <h3>{lang('SessionsTitle')}</h3>;
|
||||||
|
case SettingsScreens.ActiveWebsites:
|
||||||
|
return <h3>{lang('OtherWebSessions')}</h3>;
|
||||||
case SettingsScreens.PrivacyBlockedUsers:
|
case SettingsScreens.PrivacyBlockedUsers:
|
||||||
return <h3>{lang('BlockedUsers')}</h3>;
|
return <h3>{lang('BlockedUsers')}</h3>;
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
|||||||
sessionCount,
|
sessionCount,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
}) => {
|
}) => {
|
||||||
const { loadProfilePhotos, loadAuthorizations } = getActions();
|
const { loadProfilePhotos, loadAuthorizations, loadWebAuthorizations } = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const profileId = currentUser?.id;
|
const profileId = currentUser?.id;
|
||||||
@ -52,8 +52,9 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime) {
|
if (lastSyncTime) {
|
||||||
loadAuthorizations();
|
loadAuthorizations();
|
||||||
|
loadWebAuthorizations();
|
||||||
}
|
}
|
||||||
}, [lastSyncTime, loadAuthorizations]);
|
}, [lastSyncTime, loadAuthorizations, loadWebAuthorizations]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-content custom-scroll">
|
<div className="settings-content custom-scroll">
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type StateProps = {
|
|||||||
hasPassword?: boolean;
|
hasPassword?: boolean;
|
||||||
hasPasscode?: boolean;
|
hasPasscode?: boolean;
|
||||||
blockedCount: number;
|
blockedCount: number;
|
||||||
|
webAuthCount: number;
|
||||||
isSensitiveEnabled?: boolean;
|
isSensitiveEnabled?: boolean;
|
||||||
canChangeSensitive?: boolean;
|
canChangeSensitive?: boolean;
|
||||||
privacyPhoneNumber?: ApiPrivacySettings;
|
privacyPhoneNumber?: ApiPrivacySettings;
|
||||||
@ -34,11 +35,10 @@ type StateProps = {
|
|||||||
|
|
||||||
const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||||
isActive,
|
isActive,
|
||||||
onScreenSelect,
|
|
||||||
onReset,
|
|
||||||
hasPassword,
|
hasPassword,
|
||||||
hasPasscode,
|
hasPasscode,
|
||||||
blockedCount,
|
blockedCount,
|
||||||
|
webAuthCount,
|
||||||
isSensitiveEnabled,
|
isSensitiveEnabled,
|
||||||
canChangeSensitive,
|
canChangeSensitive,
|
||||||
privacyPhoneNumber,
|
privacyPhoneNumber,
|
||||||
@ -48,7 +48,8 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
privacyGroupChats,
|
privacyGroupChats,
|
||||||
privacyPhoneCall,
|
privacyPhoneCall,
|
||||||
privacyPhoneP2P,
|
privacyPhoneP2P,
|
||||||
|
onScreenSelect,
|
||||||
|
onReset,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadPrivacySettings,
|
loadPrivacySettings,
|
||||||
@ -114,6 +115,16 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</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
|
<ListItem
|
||||||
icon="key"
|
icon="key"
|
||||||
narrow
|
narrow
|
||||||
@ -279,6 +290,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
hasPassword,
|
hasPassword,
|
||||||
hasPasscode: Boolean(hasPasscode),
|
hasPasscode: Boolean(hasPasscode),
|
||||||
blockedCount: blocked.totalCount,
|
blockedCount: blocked.totalCount,
|
||||||
|
webAuthCount: global.activeWebSessions.orderedHashes.length,
|
||||||
isSensitiveEnabled,
|
isSensitiveEnabled,
|
||||||
canChangeSensitive,
|
canChangeSensitive,
|
||||||
privacyPhoneNumber: privacy.phoneNumber,
|
privacyPhoneNumber: privacy.phoneNumber,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { LangCode } from '../../types';
|
import type { LangCode } from '../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType,
|
ApiChat, ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType, ApiUser,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ import {
|
|||||||
selectIsMediaViewerOpen,
|
selectIsMediaViewerOpen,
|
||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectIsServiceChatReady,
|
selectIsServiceChatReady,
|
||||||
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -61,6 +62,7 @@ import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async';
|
|||||||
import WebAppModal from './WebAppModal.async';
|
import WebAppModal from './WebAppModal.async';
|
||||||
import BotTrustModal from './BotTrustModal.async';
|
import BotTrustModal from './BotTrustModal.async';
|
||||||
import BotAttachModal from './BotAttachModal.async';
|
import BotAttachModal from './BotAttachModal.async';
|
||||||
|
import UrlAuthModal from './UrlAuthModal.async';
|
||||||
|
|
||||||
import './Main.scss';
|
import './Main.scss';
|
||||||
|
|
||||||
@ -95,6 +97,8 @@ type StateProps = {
|
|||||||
webApp?: GlobalState['webApp'];
|
webApp?: GlobalState['webApp'];
|
||||||
botTrustRequest?: GlobalState['botTrustRequest'];
|
botTrustRequest?: GlobalState['botTrustRequest'];
|
||||||
botAttachRequest?: GlobalState['botAttachRequest'];
|
botAttachRequest?: GlobalState['botAttachRequest'];
|
||||||
|
currentUser?: ApiUser;
|
||||||
|
urlAuth?: GlobalState['urlAuth'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const NOTIFICATION_INTERVAL = 1000;
|
const NOTIFICATION_INTERVAL = 1000;
|
||||||
@ -134,6 +138,8 @@ const Main: FC<StateProps> = ({
|
|||||||
botTrustRequest,
|
botTrustRequest,
|
||||||
botAttachRequest,
|
botAttachRequest,
|
||||||
webApp,
|
webApp,
|
||||||
|
currentUser,
|
||||||
|
urlAuth,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
sync,
|
sync,
|
||||||
@ -369,6 +375,7 @@ const Main: FC<StateProps> = ({
|
|||||||
<Dialogs isOpen={hasDialogs} />
|
<Dialogs isOpen={hasDialogs} />
|
||||||
{audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />}
|
{audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />}
|
||||||
<SafeLinkModal url={safeLinkModalUrl} />
|
<SafeLinkModal url={safeLinkModalUrl} />
|
||||||
|
<UrlAuthModal urlAuth={urlAuth} currentUser={currentUser} />
|
||||||
<HistoryCalendar isOpen={isHistoryCalendarOpen} />
|
<HistoryCalendar isOpen={isHistoryCalendarOpen} />
|
||||||
<StickerSetModal
|
<StickerSetModal
|
||||||
isOpen={Boolean(openedStickerSetShortName)}
|
isOpen={Boolean(openedStickerSetShortName)}
|
||||||
@ -432,6 +439,7 @@ export default memo(withGlobal(
|
|||||||
const openedGame = global.openedGame;
|
const openedGame = global.openedGame;
|
||||||
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
|
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
|
||||||
const gameTitle = gameMessage?.content.game?.title;
|
const gameTitle = gameMessage?.content.game?.title;
|
||||||
|
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connectionState: global.connectionState,
|
connectionState: global.connectionState,
|
||||||
@ -463,6 +471,8 @@ export default memo(withGlobal(
|
|||||||
botTrustRequest: global.botTrustRequest,
|
botTrustRequest: global.botTrustRequest,
|
||||||
botAttachRequest: global.botAttachRequest,
|
botAttachRequest: global.botAttachRequest,
|
||||||
webApp: global.webApp,
|
webApp: global.webApp,
|
||||||
|
currentUser,
|
||||||
|
urlAuth: global.urlAuth,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Main));
|
)(Main));
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const SafeLinkModal: FC<OwnProps> = ({ url }) => {
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const handleOpen = useCallback(() => {
|
const handleOpen = useCallback(() => {
|
||||||
window.open(ensureProtocol(url));
|
window.open(ensureProtocol(url), '_blank', 'noopener');
|
||||||
toggleSafeLinkModal({ url: undefined });
|
toggleSafeLinkModal({ url: undefined });
|
||||||
}, [toggleSafeLinkModal, url]);
|
}, [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]);
|
}, [bot, isInstalled, toggleBotInAttachMenu]);
|
||||||
|
|
||||||
|
const handleCloseClick = useCallback(() => {
|
||||||
|
closeWebApp();
|
||||||
|
}, [closeWebApp]);
|
||||||
|
|
||||||
const openBotChat = useCallback(() => {
|
const openBotChat = useCallback(() => {
|
||||||
openChat({
|
openChat({
|
||||||
id: bot!.id,
|
id: bot!.id,
|
||||||
@ -197,7 +201,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
color="translucent"
|
color="translucent"
|
||||||
size="smaller"
|
size="smaller"
|
||||||
ariaLabel={lang('Close')}
|
ariaLabel={lang('Close')}
|
||||||
onClick={closeWebApp}
|
onClick={handleCloseClick}
|
||||||
>
|
>
|
||||||
<i className="icon-close" />
|
<i className="icon-close" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -219,7 +223,9 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</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 prevMainButtonColor = usePrevious(mainButton?.color, true);
|
||||||
const prevMainButtonTextColor = usePrevious(mainButton?.textColor, true);
|
const prevMainButtonTextColor = usePrevious(mainButton?.textColor, true);
|
||||||
|
|||||||
@ -4,8 +4,9 @@
|
|||||||
max-width: var(--max-width);
|
max-width: var(--max-width);
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
grid-auto-flow: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
|
||||||
import React 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 type { ApiKeyboardButton, ApiMessage } from '../../../api/types';
|
||||||
|
|
||||||
import { RE_TME_LINK } from '../../../config';
|
import { RE_TME_LINK } from '../../../config';
|
||||||
@ -18,6 +18,30 @@ type OwnProps = {
|
|||||||
|
|
||||||
const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
||||||
const lang = useLang();
|
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 (
|
return (
|
||||||
<div className="InlineButtons">
|
<div className="InlineButtons">
|
||||||
{message.inlineButtons!.map((row) => (
|
{message.inlineButtons!.map((row) => (
|
||||||
@ -31,10 +55,7 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
|||||||
onClick={() => onClick({ messageId: message.id, button })}
|
onClick={() => onClick({ messageId: message.id, button })}
|
||||||
>
|
>
|
||||||
<span className="inline-button-text">{renderText(lang(button.text))}</span>
|
<span className="inline-button-text">{renderText(lang(button.text))}</span>
|
||||||
{['buy', 'receipt'].includes(button.type) && <i className="icon-card" />}
|
{renderIcon(button)}
|
||||||
{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" />}
|
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -108,7 +108,7 @@ const Location: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
const url = prepareMapUrl(point.lat, point.long, zoom);
|
const url = prepareMapUrl(point.lat, point.long, zoom);
|
||||||
window.open(url, '_blank')?.focus();
|
window.open(url, '_blank', 'noopener')?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCountdown = useCallback((countdownEl: HTMLDivElement) => {
|
const updateCountdown = useCallback((countdownEl: HTMLDivElement) => {
|
||||||
|
|||||||
@ -23,6 +23,7 @@ type OwnProps = {
|
|||||||
blocking?: boolean;
|
blocking?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
withCheckedCallback?: boolean;
|
withCheckedCallback?: boolean;
|
||||||
|
className?: string;
|
||||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||||
onCheck?: (isChecked: boolean) => void;
|
onCheck?: (isChecked: boolean) => void;
|
||||||
};
|
};
|
||||||
@ -39,6 +40,7 @@ const Checkbox: FC<OwnProps> = ({
|
|||||||
round,
|
round,
|
||||||
blocking,
|
blocking,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
className,
|
||||||
onChange,
|
onChange,
|
||||||
onCheck,
|
onCheck,
|
||||||
}) => {
|
}) => {
|
||||||
@ -53,16 +55,17 @@ const Checkbox: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [onChange, onCheck]);
|
}, [onChange, onCheck]);
|
||||||
|
|
||||||
const className = buildClassName(
|
const labelClassName = buildClassName(
|
||||||
'Checkbox',
|
'Checkbox',
|
||||||
disabled && 'disabled',
|
disabled && 'disabled',
|
||||||
round && 'round',
|
round && 'round',
|
||||||
isLoading && 'loading',
|
isLoading && 'loading',
|
||||||
blocking && 'blocking',
|
blocking && 'blocking',
|
||||||
|
className,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className={className} dir={lang.isRtl ? 'rtl' : undefined}>
|
<label className={labelClassName} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={id}
|
id={id}
|
||||||
@ -74,7 +77,7 @@ const Checkbox: FC<OwnProps> = ({
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<div className="Checkbox-main">
|
<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>}
|
{subLabel && <span className="subLabel" dir="auto">{renderText(subLabel)}</span>}
|
||||||
</div>
|
</div>
|
||||||
{isLoading && <Spinner />}
|
{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,
|
addActionHandler, getActions, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} 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 type { InlineBotSettings } from '../../../types';
|
||||||
|
|
||||||
import {
|
|
||||||
RE_TG_LINK, RE_TME_LINK,
|
|
||||||
} from '../../../config';
|
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import {
|
import {
|
||||||
selectBot,
|
selectBot,
|
||||||
@ -35,11 +34,7 @@ addActionHandler('clickBotInlineButton', (global, actions, payload) => {
|
|||||||
break;
|
break;
|
||||||
case 'url': {
|
case 'url': {
|
||||||
const { url } = button;
|
const { url } = button;
|
||||||
if (url.match(RE_TME_LINK) || url.match(RE_TG_LINK)) {
|
actions.openUrl({ url });
|
||||||
actions.openTelegramLink({ url });
|
|
||||||
} else {
|
|
||||||
actions.toggleSafeLinkModal({ url });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'callback': {
|
case 'callback': {
|
||||||
@ -154,6 +149,20 @@ addActionHandler('clickBotInlineButton', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
break;
|
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({
|
async function searchInlineBot({
|
||||||
username,
|
username,
|
||||||
inlineBotData,
|
inlineBotData,
|
||||||
@ -691,7 +811,7 @@ let gameePopups: PopupManager | undefined;
|
|||||||
|
|
||||||
async function answerCallbackButton(chat: ApiChat, messageId: number, data?: string, isGame = false) {
|
async function answerCallbackButton(chat: ApiChat, messageId: number, data?: string, isGame = false) {
|
||||||
const {
|
const {
|
||||||
showDialog, showNotification, toggleSafeLinkModal, openGame,
|
showDialog, showNotification, openUrl, openGame,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
if (isGame) {
|
if (isGame) {
|
||||||
@ -731,7 +851,7 @@ async function answerCallbackButton(chat: ApiChat, messageId: number, data?: str
|
|||||||
openGame({ url, chatId: chat.id, messageId });
|
openGame({ url, chatId: chat.id, messageId });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toggleSafeLinkModal({ url });
|
openUrl({ url });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import { LoadMoreDirection } from '../../../types';
|
|||||||
import {
|
import {
|
||||||
MAX_MEDIA_FILES_FOR_ALBUM,
|
MAX_MEDIA_FILES_FOR_ALBUM,
|
||||||
MESSAGE_LIST_SLICE,
|
MESSAGE_LIST_SLICE,
|
||||||
|
RE_TG_LINK,
|
||||||
|
RE_TME_LINK,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { IS_IOS } from '../../../util/environment';
|
import { IS_IOS } from '../../../util/environment';
|
||||||
@ -70,6 +72,9 @@ import {
|
|||||||
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers';
|
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers';
|
||||||
import { getMessageOriginalId, isServiceNotificationMessage } from '../../helpers';
|
import { getMessageOriginalId, isServiceNotificationMessage } from '../../helpers';
|
||||||
import { getTranslation } from '../../../util/langProvider';
|
import { getTranslation } from '../../../util/langProvider';
|
||||||
|
import { ensureProtocol } from '../../../util/ensureProtocol';
|
||||||
|
|
||||||
|
const AUTOLOGIN_TOKEN_KEY = 'autologin_token';
|
||||||
|
|
||||||
const uploadProgressCallbacks = new Map<number, ApiOnProgress>();
|
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) {
|
function countSortedIds(ids: number[], from: number, to: number) {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
|
|||||||
@ -252,6 +252,13 @@ function migrateCache(cached: GlobalState, initialState: GlobalState) {
|
|||||||
orderedHashes: [],
|
orderedHashes: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cached.activeWebSessions) {
|
||||||
|
cached.activeWebSessions = {
|
||||||
|
byHash: {},
|
||||||
|
orderedHashes: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCache() {
|
function updateCache() {
|
||||||
|
|||||||
@ -143,6 +143,11 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
orderedHashes: [],
|
orderedHashes: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
activeWebSessions: {
|
||||||
|
byHash: {},
|
||||||
|
orderedHashes: [],
|
||||||
|
},
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
byKey: {
|
byKey: {
|
||||||
theme: 'light',
|
theme: 'light',
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import type {
|
|||||||
ApiThemeParameters,
|
ApiThemeParameters,
|
||||||
ApiAttachMenuBot,
|
ApiAttachMenuBot,
|
||||||
ApiPhoneCall,
|
ApiPhoneCall,
|
||||||
|
ApiWebSession,
|
||||||
} from '../api/types';
|
} from '../api/types';
|
||||||
import type {
|
import type {
|
||||||
FocusDirection,
|
FocusDirection,
|
||||||
@ -490,6 +491,11 @@ export type GlobalState = {
|
|||||||
ttlDays?: number;
|
ttlDays?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
activeWebSessions: {
|
||||||
|
byHash: Record<string, ApiWebSession>;
|
||||||
|
orderedHashes: string[];
|
||||||
|
};
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
byKey: ISettings;
|
byKey: ISettings;
|
||||||
loadedWallpapers?: ApiWallpaper[];
|
loadedWallpapers?: ApiWallpaper[];
|
||||||
@ -589,6 +595,20 @@ export type GlobalState = {
|
|||||||
hash?: string;
|
hash?: string;
|
||||||
bots: Record<string, ApiAttachMenuBot>;
|
bots: Record<string, ApiAttachMenuBot>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
urlAuth?: {
|
||||||
|
button?: {
|
||||||
|
chatId: string;
|
||||||
|
messageId: number;
|
||||||
|
buttonId: number;
|
||||||
|
};
|
||||||
|
request?: {
|
||||||
|
domain: string;
|
||||||
|
botId: string;
|
||||||
|
shouldRequestWriteAccess?: boolean;
|
||||||
|
};
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CallSound = (
|
export type CallSound = (
|
||||||
@ -637,7 +657,7 @@ export interface ActionPayloads {
|
|||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
resetOpenChatWithText: {};
|
resetOpenChatWithText: never;
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
setEditingDraft: {
|
setEditingDraft: {
|
||||||
@ -657,10 +677,10 @@ export interface ActionPayloads {
|
|||||||
animateUnreadReaction: {
|
animateUnreadReaction: {
|
||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
};
|
};
|
||||||
focusNextReaction: {};
|
focusNextReaction: never;
|
||||||
focusNextMention: {};
|
focusNextMention: never;
|
||||||
readAllReactions: {};
|
readAllReactions: never;
|
||||||
readAllMentions: {};
|
readAllMentions: never;
|
||||||
markMentionsRead: {
|
markMentionsRead: {
|
||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
};
|
};
|
||||||
@ -677,7 +697,7 @@ export interface ActionPayloads {
|
|||||||
playbackRate?: number;
|
playbackRate?: number;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
};
|
};
|
||||||
closeMediaViewer: {};
|
closeMediaViewer: never;
|
||||||
setMediaViewerVolume: {
|
setMediaViewerVolume: {
|
||||||
volume: number;
|
volume: number;
|
||||||
};
|
};
|
||||||
@ -697,7 +717,7 @@ export interface ActionPayloads {
|
|||||||
playbackRate?: number;
|
playbackRate?: number;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
};
|
};
|
||||||
closeAudioPlayer: {};
|
closeAudioPlayer: never;
|
||||||
setAudioPlayerVolume: {
|
setAudioPlayerVolume: {
|
||||||
volume: number;
|
volume: number;
|
||||||
};
|
};
|
||||||
@ -712,7 +732,7 @@ export interface ActionPayloads {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Downloads
|
// Downloads
|
||||||
downloadSelectedMessages: {};
|
downloadSelectedMessages: never;
|
||||||
downloadMessageMedia: {
|
downloadMessageMedia: {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
};
|
};
|
||||||
@ -787,14 +807,14 @@ export interface ActionPayloads {
|
|||||||
isSamePeer?: boolean;
|
isSamePeer?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
resetSwitchBotInline: {};
|
resetSwitchBotInline: never;
|
||||||
|
|
||||||
openGame: {
|
openGame: {
|
||||||
url: string;
|
url: string;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
};
|
};
|
||||||
closeGame: {};
|
closeGame: never;
|
||||||
|
|
||||||
requestWebView: {
|
requestWebView: {
|
||||||
url?: string;
|
url?: string;
|
||||||
@ -819,9 +839,9 @@ export interface ActionPayloads {
|
|||||||
buttonText: string;
|
buttonText: string;
|
||||||
theme?: ApiThemeParameters;
|
theme?: ApiThemeParameters;
|
||||||
};
|
};
|
||||||
closeWebApp: {};
|
closeWebApp: never;
|
||||||
|
|
||||||
cancelBotTrustRequest: {};
|
cancelBotTrustRequest: never;
|
||||||
markBotTrusted: {
|
markBotTrusted: {
|
||||||
botId: string;
|
botId: string;
|
||||||
};
|
};
|
||||||
@ -852,11 +872,52 @@ export interface ActionPayloads {
|
|||||||
startParam?: string;
|
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
|
// Misc
|
||||||
openPollModal: {
|
openPollModal: {
|
||||||
isQuiz?: boolean;
|
isQuiz?: boolean;
|
||||||
};
|
};
|
||||||
closePollModal: {};
|
closePollModal: never;
|
||||||
|
|
||||||
|
openUrl: {
|
||||||
|
url: string;
|
||||||
|
shouldSkipModal?: boolean;
|
||||||
|
};
|
||||||
|
toggleSafeLinkModal: {
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
closeUrlAuthModal: never;
|
||||||
|
|
||||||
// Calls
|
// Calls
|
||||||
requestCall: {
|
requestCall: {
|
||||||
@ -864,17 +925,17 @@ export interface ActionPayloads {
|
|||||||
isVideo?: boolean;
|
isVideo?: boolean;
|
||||||
};
|
};
|
||||||
sendSignalingData: P2pMessage;
|
sendSignalingData: P2pMessage;
|
||||||
hangUp: {};
|
hangUp: never;
|
||||||
acceptCall: {};
|
acceptCall: never;
|
||||||
setCallRating: {
|
setCallRating: {
|
||||||
rating: number;
|
rating: number;
|
||||||
comment: string;
|
comment: string;
|
||||||
};
|
};
|
||||||
closeCallRatingModal: {};
|
closeCallRatingModal: never;
|
||||||
playGroupCallSound: {
|
playGroupCallSound: {
|
||||||
sound: CallSound;
|
sound: CallSound;
|
||||||
};
|
};
|
||||||
connectToActivePhoneCall: {};
|
connectToActivePhoneCall: never;
|
||||||
|
|
||||||
// Passcode
|
// Passcode
|
||||||
setPasscode: { passcode: string };
|
setPasscode: { passcode: string };
|
||||||
@ -959,7 +1020,6 @@ export type NonTypedActionNames = (
|
|||||||
'setSettingOption' | 'loadPasswordInfo' | 'clearTwoFaError' |
|
'setSettingOption' | 'loadPasswordInfo' | 'clearTwoFaError' |
|
||||||
'updatePassword' | 'updateRecoveryEmail' | 'clearPassword' | 'provideTwoFaEmailCode' | 'checkPassword' |
|
'updatePassword' | 'updateRecoveryEmail' | 'clearPassword' | 'provideTwoFaEmailCode' | 'checkPassword' |
|
||||||
'loadBlockedContacts' | 'blockContact' | 'unblockContact' |
|
'loadBlockedContacts' | 'blockContact' | 'unblockContact' |
|
||||||
'loadAuthorizations' | 'terminateAuthorization' | 'terminateAllAuthorizations' |
|
|
||||||
'loadNotificationSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings' |
|
'loadNotificationSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings' |
|
||||||
'updateWebNotificationSettings' | 'loadLanguages' | 'loadPrivacySettings' | 'setPrivacyVisibility' |
|
'updateWebNotificationSettings' | 'loadLanguages' | 'loadPrivacySettings' | 'setPrivacyVisibility' |
|
||||||
'setPrivacySettings' | 'loadNotificationExceptions' | 'setThemeSettings' | 'updateIsOnline' |
|
'setPrivacySettings' | 'loadNotificationExceptions' | 'setThemeSettings' | 'updateIsOnline' |
|
||||||
@ -987,7 +1047,7 @@ export type NonTypedActionNames = (
|
|||||||
'loadMoreGroupCallParticipants' | 'connectToActiveGroupCall' |
|
'loadMoreGroupCallParticipants' | 'connectToActiveGroupCall' |
|
||||||
// stats
|
// stats
|
||||||
'loadStatistics' | 'loadMessageStatistics' | 'loadStatisticsAsyncGraph'
|
'loadStatistics' | 'loadMessageStatistics' | 'loadStatisticsAsyncGraph'
|
||||||
);
|
);
|
||||||
|
|
||||||
const typed = typify<GlobalState, ActionPayloads, NonTypedActionNames>();
|
const typed = typify<GlobalState, ActionPayloads, NonTypedActionNames>();
|
||||||
export type GlobalActions = ReturnType<typeof typed.getActions>;
|
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.sendConfirmPhoneCode#1b3faa88 hash:string settings:CodeSettings = auth.SentCode;
|
||||||
account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
|
account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
|
||||||
account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword;
|
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.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode;
|
||||||
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
||||||
account.getContactSignUpNotification#9f07c728 = Bool;
|
account.getContactSignUpNotification#9f07c728 = Bool;
|
||||||
@ -1115,6 +1118,8 @@ messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
|
|||||||
messages.editChatAbout#def60797 peer:InputPeer about:string = Bool;
|
messages.editChatAbout#def60797 peer:InputPeer about:string = Bool;
|
||||||
messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates;
|
messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates;
|
||||||
messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference;
|
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.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool;
|
||||||
messages.getScheduledHistory#f516760b peer:InputPeer hash:long = messages.Messages;
|
messages.getScheduledHistory#f516760b peer:InputPeer hash:long = messages.Messages;
|
||||||
messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
|
messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
|
||||||
|
|||||||
@ -35,6 +35,9 @@
|
|||||||
"account.sendConfirmPhoneCode",
|
"account.sendConfirmPhoneCode",
|
||||||
"account.confirmPhone",
|
"account.confirmPhone",
|
||||||
"account.getTmpPassword",
|
"account.getTmpPassword",
|
||||||
|
"account.getWebAuthorizations",
|
||||||
|
"account.resetWebAuthorization",
|
||||||
|
"account.resetWebAuthorizations",
|
||||||
"account.sendVerifyPhoneCode",
|
"account.sendVerifyPhoneCode",
|
||||||
"account.confirmPasswordEmail",
|
"account.confirmPasswordEmail",
|
||||||
"account.getContactSignUpNotification",
|
"account.getContactSignUpNotification",
|
||||||
@ -131,6 +134,8 @@
|
|||||||
"messages.editChatAbout",
|
"messages.editChatAbout",
|
||||||
"messages.editChatDefaultBannedRights",
|
"messages.editChatDefaultBannedRights",
|
||||||
"messages.getEmojiKeywordsDifference",
|
"messages.getEmojiKeywordsDifference",
|
||||||
|
"messages.requestUrlAuth",
|
||||||
|
"messages.acceptUrlAuth",
|
||||||
"messages.hidePeerSettingsBar",
|
"messages.hidePeerSettingsBar",
|
||||||
"messages.getScheduledHistory",
|
"messages.getScheduledHistory",
|
||||||
"messages.sendScheduledMessages",
|
"messages.sendScheduledMessages",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -51,6 +51,9 @@
|
|||||||
.icon-volume-3:before {
|
.icon-volume-3:before {
|
||||||
content: "\e991";
|
content: "\e991";
|
||||||
}
|
}
|
||||||
|
.icon-web:before {
|
||||||
|
content: "\e99b";
|
||||||
|
}
|
||||||
.icon-key:before {
|
.icon-key:before {
|
||||||
content: "\e99a";
|
content: "\e99a";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -214,6 +214,7 @@ export enum SettingsScreens {
|
|||||||
TwoFaRecoveryEmailCode,
|
TwoFaRecoveryEmailCode,
|
||||||
TwoFaCongratulations,
|
TwoFaCongratulations,
|
||||||
QuickReaction,
|
QuickReaction,
|
||||||
|
ActiveWebsites,
|
||||||
PasscodeDisabled,
|
PasscodeDisabled,
|
||||||
PasscodeNewPasscode,
|
PasscodeNewPasscode,
|
||||||
PasscodeNewPasscodeConfirm,
|
PasscodeNewPasscodeConfirm,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user