Settings: Active Sessions 2.0 (#1843)
This commit is contained in:
parent
67da677b1b
commit
d7bcf7a769
@ -36,6 +36,7 @@ export function buildApiSession(session: GramJs.Authorization): ApiSession {
|
|||||||
isOfficialApp: Boolean(session.officialApp),
|
isOfficialApp: Boolean(session.officialApp),
|
||||||
isPasswordPending: Boolean(session.passwordPending),
|
isPasswordPending: Boolean(session.passwordPending),
|
||||||
hash: String(session.hash),
|
hash: String(session.hash),
|
||||||
|
areCallsEnabled: !session.callRequestsDisabled,
|
||||||
...pick(session, [
|
...pick(session, [
|
||||||
'deviceModel', 'platform', 'systemVersion', 'appName', 'appVersion', 'dateCreated', 'dateActive',
|
'deviceModel', 'platform', 'systemVersion', 'appName', 'appVersion', 'dateCreated', 'dateActive',
|
||||||
'ip', 'country', 'region',
|
'ip', 'country', 'region',
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import BigInt from 'big-integer';
|
||||||
import {
|
import {
|
||||||
ApiChat, ApiPhoto, ApiReportReason, ApiUser,
|
ApiChat, ApiPhoto, ApiReportReason, ApiUser,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
@ -41,3 +42,28 @@ export async function reportProfilePhoto({
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function changeSessionSettings({
|
||||||
|
hash, areCallsEnabled,
|
||||||
|
}: {
|
||||||
|
hash: string; areCallsEnabled: boolean;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.account.ChangeAuthorizationSettings({
|
||||||
|
hash: BigInt(hash),
|
||||||
|
callRequestsDisabled: !areCallsEnabled,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function changeSessionTtl({
|
||||||
|
days,
|
||||||
|
}: {
|
||||||
|
days: number;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.account.SetAuthorizationTTL({
|
||||||
|
authorizationTtlDays: days,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ export {
|
|||||||
} from './client';
|
} from './client';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
reportPeer, reportProfilePhoto,
|
reportPeer, reportProfilePhoto, changeSessionSettings, changeSessionTtl,
|
||||||
} from './account';
|
} from './account';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -160,7 +160,10 @@ export async function fetchAuthorizations() {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.authorizations.map(buildApiSession);
|
return {
|
||||||
|
authorizations: buildCollectionByKey(result.authorizations.map(buildApiSession), 'hash'),
|
||||||
|
ttlDays: result.authorizationTtlDays,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function terminateAuthorization(hash: string) {
|
export function terminateAuthorization(hash: string) {
|
||||||
|
|||||||
@ -61,6 +61,7 @@ export interface ApiSession {
|
|||||||
ip: string;
|
ip: string;
|
||||||
country: string;
|
country: string;
|
||||||
region: string;
|
region: string;
|
||||||
|
areCallsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiSessionData {
|
export interface ApiSessionData {
|
||||||
|
|||||||
@ -0,0 +1,52 @@
|
|||||||
|
$icons: "android", "apple", "brave", "chrome", "edge", "firefox", "linux", "opera", "safari", "samsung", "ubuntu", "unknown", "vivaldi", "windows", "xbox";
|
||||||
|
|
||||||
|
@mixin device-icon($icon-name) {
|
||||||
|
.iconDevice__#{$icon-name} {
|
||||||
|
background-image: url("../../../assets/devices/#{$icon-name}.svg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.SettingsActiveSession {
|
||||||
|
:global(.modal-dialog) {
|
||||||
|
max-width: 28rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconDevice {
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 5rem;
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $icon in $icons {
|
||||||
|
@include device-icon($icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionHeader {
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionName {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
111
src/components/left/settings/SettingsActiveSession.tsx
Normal file
111
src/components/left/settings/SettingsActiveSession.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import React, { FC, memo, useCallback } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import { ApiSession } from '../../../api/types';
|
||||||
|
|
||||||
|
import { formatDateTimeToString } from '../../../util/dateFormat';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
|
import getSessionIcon from './helpers/getSessionIcon';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
import Switcher from '../../ui/Switcher';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
|
import styles from './SettingsActiveSession.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
hash?: string;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
session?: ApiSession;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsActiveSession: FC<OwnProps & StateProps> = ({
|
||||||
|
isOpen, session, onClose,
|
||||||
|
}) => {
|
||||||
|
const { changeSessionSettings, terminateAuthorization } = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const renderingSession = useCurrentOrPrev(session, true);
|
||||||
|
|
||||||
|
const handleCallsStateChange = useCallback(() => {
|
||||||
|
changeSessionSettings({
|
||||||
|
hash: session!.hash,
|
||||||
|
areCallsEnabled: !session?.areCallsEnabled,
|
||||||
|
});
|
||||||
|
}, [changeSessionSettings, session]);
|
||||||
|
|
||||||
|
const handleTerminateSessionClick = useCallback(() => {
|
||||||
|
terminateAuthorization({ hash: session!.hash });
|
||||||
|
onClose();
|
||||||
|
}, [onClose, session, terminateAuthorization]);
|
||||||
|
|
||||||
|
if (!renderingSession) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={lang('SessionPreview.Title')}
|
||||||
|
isOpen={isOpen}
|
||||||
|
hasCloseButton
|
||||||
|
onClose={onClose}
|
||||||
|
className={styles.SettingsActiveSession}
|
||||||
|
>
|
||||||
|
<div className={buildClassName(
|
||||||
|
styles.iconDevice,
|
||||||
|
renderingSession && styles[`iconDevice__${getSessionIcon(renderingSession)}`],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<h3 className={styles.title} dir="auto">{renderingSession?.deviceModel}</h3>
|
||||||
|
<div className={styles.date} aria-label={lang('PrivacySettings.LastSeen')}>
|
||||||
|
{formatDateTimeToString(renderingSession.dateActive * 1000, lang.code)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl className={styles.box}>
|
||||||
|
<dt>{lang('SessionPreview.App')}</dt>
|
||||||
|
<dd>
|
||||||
|
{renderingSession?.appName} {renderingSession?.appVersion},
|
||||||
|
{renderingSession?.platform} {renderingSession?.systemVersion}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>{lang('SessionPreview.Ip')}</dt>
|
||||||
|
<dd>{renderingSession?.ip}</dd>
|
||||||
|
|
||||||
|
<dt>{lang('SessionPreview.Location')}</dt>
|
||||||
|
<dd>{renderingSession && getLocation(renderingSession)}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<p className={styles.note}>{lang('SessionPreview.IpDesc')}</p>
|
||||||
|
|
||||||
|
<h4 className={styles.actionHeader}>{lang('SessionPreview.AcceptHeader')}</h4>
|
||||||
|
|
||||||
|
<ListItem onClick={handleCallsStateChange}>
|
||||||
|
<span className={styles.actionName}>{lang('SessionPreview.Accept.Calls')}</span>
|
||||||
|
<Switcher
|
||||||
|
id="darkmode"
|
||||||
|
label="On"
|
||||||
|
checked={renderingSession.areCallsEnabled}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Button color="danger" onClick={handleTerminateSessionClick}>{lang('SessionPreview.TerminateSession')}</Button>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLocation(session: ApiSession) {
|
||||||
|
return [session.region, session.country].filter(Boolean).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>((global, { hash }) => {
|
||||||
|
return {
|
||||||
|
session: hash ? global.activeSessions.byHash[hash] : undefined,
|
||||||
|
};
|
||||||
|
})(SettingsActiveSession));
|
||||||
@ -1,5 +1,6 @@
|
|||||||
|
/* eslint-disable react/jsx-no-bind */
|
||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useMemo,
|
FC, memo, useCallback, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -14,8 +15,10 @@ import getSessionIcon from './helpers/getSessionIcon';
|
|||||||
|
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
|
import SettingsActiveSession from './SettingsActiveSession';
|
||||||
|
|
||||||
import './SettingsActiveSessions.scss';
|
import './SettingsActiveSessions.scss';
|
||||||
|
import RadioGroup from '../../ui/RadioGroup';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
@ -24,21 +27,63 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
activeSessions: ApiSession[];
|
byHash: Record<string, ApiSession>;
|
||||||
|
orderedHashes: string[];
|
||||||
|
ttlDays?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
|
const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
|
||||||
isActive,
|
isActive,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onReset,
|
onReset,
|
||||||
activeSessions,
|
byHash,
|
||||||
|
orderedHashes,
|
||||||
|
ttlDays,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
terminateAuthorization,
|
terminateAuthorization,
|
||||||
terminateAllAuthorizations,
|
terminateAllAuthorizations,
|
||||||
|
changeSessionTtl,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
const [isConfirmTerminateAllDialogOpen, openConfirmTerminateAllDialog, closeConfirmTerminateAllDialog] = useFlag();
|
const [isConfirmTerminateAllDialogOpen, openConfirmTerminateAllDialog, closeConfirmTerminateAllDialog] = useFlag();
|
||||||
|
const [openedSessionHash, setOpenedSessionHash] = useState<string | undefined>();
|
||||||
|
const [isModalOpen, openModal, closeModal] = useFlag();
|
||||||
|
|
||||||
|
const autoTerminateValue = useMemo(() => {
|
||||||
|
if (ttlDays === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (ttlDays <= 7) {
|
||||||
|
return '7';
|
||||||
|
}
|
||||||
|
if (ttlDays <= 30) {
|
||||||
|
return '30';
|
||||||
|
}
|
||||||
|
if (ttlDays <= 90) {
|
||||||
|
return '90';
|
||||||
|
}
|
||||||
|
if (ttlDays <= 180) {
|
||||||
|
return '180';
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [ttlDays]);
|
||||||
|
|
||||||
|
const AUTO_TERMINATE_OPTIONS = useMemo(() => [{
|
||||||
|
label: lang('Weeks', 1, 'i'),
|
||||||
|
value: '7',
|
||||||
|
}, {
|
||||||
|
label: lang('Months', 1, 'i'),
|
||||||
|
value: '30',
|
||||||
|
}, {
|
||||||
|
label: lang('Months', 3, 'i'),
|
||||||
|
value: '90',
|
||||||
|
}, {
|
||||||
|
label: lang('Months', 6, 'i'),
|
||||||
|
value: '180',
|
||||||
|
}], [lang]);
|
||||||
|
|
||||||
const handleTerminateSessionClick = useCallback((hash: string) => {
|
const handleTerminateSessionClick = useCallback((hash: string) => {
|
||||||
terminateAuthorization({ hash });
|
terminateAuthorization({ hash });
|
||||||
@ -49,15 +94,30 @@ const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
|
|||||||
terminateAllAuthorizations();
|
terminateAllAuthorizations();
|
||||||
}, [closeConfirmTerminateAllDialog, terminateAllAuthorizations]);
|
}, [closeConfirmTerminateAllDialog, terminateAllAuthorizations]);
|
||||||
|
|
||||||
|
const handleOpenSessionModal = useCallback((hash: string) => {
|
||||||
|
setOpenedSessionHash(hash);
|
||||||
|
openModal();
|
||||||
|
}, [openModal]);
|
||||||
|
|
||||||
|
const handleCloseSessionModal = useCallback(() => {
|
||||||
|
setOpenedSessionHash(undefined);
|
||||||
|
closeModal();
|
||||||
|
}, [closeModal]);
|
||||||
|
|
||||||
|
const handleChangeSessionTtl = useCallback((value: string) => {
|
||||||
|
changeSessionTtl({ days: Number(value) });
|
||||||
|
}, [changeSessionTtl]);
|
||||||
|
|
||||||
const currentSession = useMemo(() => {
|
const currentSession = useMemo(() => {
|
||||||
return activeSessions.find((session) => session.isCurrent);
|
const currentSessionHash = orderedHashes.find((hash) => byHash[hash].isCurrent);
|
||||||
}, [activeSessions]);
|
|
||||||
|
|
||||||
const otherSessions = useMemo(() => {
|
return currentSessionHash ? byHash[currentSessionHash] : undefined;
|
||||||
return activeSessions.filter((session) => !session.isCurrent);
|
}, [byHash, orderedHashes]);
|
||||||
}, [activeSessions]);
|
|
||||||
|
|
||||||
const lang = useLang();
|
const otherSessionHashes = useMemo(() => {
|
||||||
|
return orderedHashes.filter((hash) => !byHash[hash].isCurrent);
|
||||||
|
}, [byHash, orderedHashes]);
|
||||||
|
const hasOtherSessions = Boolean(otherSessionHashes.length);
|
||||||
|
|
||||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.ActiveSessions);
|
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.ActiveSessions);
|
||||||
|
|
||||||
@ -78,32 +138,54 @@ const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem
|
{hasOtherSessions && (
|
||||||
className="destructive mb-0 no-icon"
|
<ListItem
|
||||||
icon="stop"
|
className="destructive mb-0 no-icon"
|
||||||
ripple
|
icon="stop"
|
||||||
narrow
|
ripple
|
||||||
onClick={openConfirmTerminateAllDialog}
|
narrow
|
||||||
>
|
onClick={openConfirmTerminateAllDialog}
|
||||||
{lang('TerminateAllSessions')}
|
>
|
||||||
</ListItem>
|
{lang('TerminateAllSessions')}
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOtherSessions(sessions: ApiSession[]) {
|
function renderOtherSessions(sessionHashes: string[]) {
|
||||||
return (
|
return (
|
||||||
<div className="settings-item">
|
<div className="settings-item">
|
||||||
<h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>
|
<h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
{lang('OtherSessions')}
|
{lang('OtherSessions')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{sessions.map(renderSession)}
|
{sessionHashes.map(renderSession)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSession(session: ApiSession) {
|
function renderAutoTerminate() {
|
||||||
|
return (
|
||||||
|
<div className="settings-item">
|
||||||
|
<h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{lang('TerminateOldSessionHeader')}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p>{lang('IfInactiveFor')}</p>
|
||||||
|
<RadioGroup
|
||||||
|
name="session_ttl"
|
||||||
|
options={AUTO_TERMINATE_OPTIONS}
|
||||||
|
selected={autoTerminateValue}
|
||||||
|
onChange={handleChangeSessionTtl}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSession(sessionHash: string) {
|
||||||
|
const session = byHash[sessionHash];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={session.hash}
|
key={session.hash}
|
||||||
@ -117,6 +199,7 @@ const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
|
|||||||
},
|
},
|
||||||
}]}
|
}]}
|
||||||
icon={`device-${getSessionIcon(session)} icon-device`}
|
icon={`device-${getSessionIcon(session)} icon-device`}
|
||||||
|
onClick={() => { handleOpenSessionModal(session.hash); }}
|
||||||
>
|
>
|
||||||
<div className="multiline-menu-item full-size" dir="auto">
|
<div className="multiline-menu-item full-size" dir="auto">
|
||||||
<span className="date">{formatPastTimeShort(lang, session.dateActive * 1000)}</span>
|
<span className="date">{formatPastTimeShort(lang, session.dateActive * 1000)}</span>
|
||||||
@ -133,17 +216,19 @@ const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content custom-scroll SettingsActiveSessions">
|
<div className="settings-content custom-scroll SettingsActiveSessions">
|
||||||
{currentSession && renderCurrentSession(currentSession)}
|
{currentSession && renderCurrentSession(currentSession)}
|
||||||
{otherSessions && renderOtherSessions(otherSessions)}
|
{hasOtherSessions && renderOtherSessions(otherSessionHashes)}
|
||||||
{otherSessions && (
|
{renderAutoTerminate()}
|
||||||
|
{hasOtherSessions && (
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isConfirmTerminateAllDialogOpen}
|
isOpen={isConfirmTerminateAllDialogOpen}
|
||||||
onClose={closeConfirmTerminateAllDialog}
|
onClose={closeConfirmTerminateAllDialog}
|
||||||
text="Are you sure you want to terminate all other sessions?"
|
text={lang('AreYouSureSessions')}
|
||||||
confirmLabel="Terminate All Other Sessions"
|
confirmLabel={lang('TerminateAllSessions')}
|
||||||
confirmHandler={handleTerminateAllSessions}
|
confirmHandler={handleTerminateAllSessions}
|
||||||
confirmIsDestructive
|
confirmIsDestructive
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<SettingsActiveSession isOpen={isModalOpen} hash={openedSessionHash} onClose={handleCloseSessionModal} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -153,9 +238,5 @@ function getLocation(session: ApiSession) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => global.activeSessions,
|
||||||
return {
|
|
||||||
activeSessions: global.activeSessions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
)(SettingsActiveSessions));
|
)(SettingsActiveSessions));
|
||||||
|
|||||||
@ -127,7 +127,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { currentUserId, lastSyncTime } = global;
|
const { currentUserId, lastSyncTime } = global;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionCount: global.activeSessions.length,
|
sessionCount: global.activeSessions.orderedHashes.length,
|
||||||
currentUser: currentUserId ? selectUser(global, currentUserId) : undefined,
|
currentUser: currentUserId ? selectUser(global, currentUserId) : undefined,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { addActionHandler } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import { selectChat } from '../../selectors';
|
import { selectChat } from '../../selectors';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { getTranslation } from '../../../util/langProvider';
|
import { getTranslation } from '../../../util/langProvider';
|
||||||
@ -60,3 +60,111 @@ addActionHandler('reportProfilePhoto', async (global, actions, payload) => {
|
|||||||
: 'An error occurred while submitting your report. Please, try again later.',
|
: 'An error occurred while submitting your report. Please, try again later.',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadAuthorizations', async () => {
|
||||||
|
const result = await callApi('fetchAuthorizations');
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobal({
|
||||||
|
...getGlobal(),
|
||||||
|
activeSessions: {
|
||||||
|
byHash: result.authorizations,
|
||||||
|
orderedHashes: Object.keys(result.authorizations),
|
||||||
|
ttlDays: result.ttlDays,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('terminateAuthorization', async (global, actions, payload) => {
|
||||||
|
const { hash } = payload!;
|
||||||
|
|
||||||
|
const result = await callApi('terminateAuthorization', hash);
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
const { [hash]: removedSessions, ...newSessions } = global.activeSessions.byHash;
|
||||||
|
|
||||||
|
setGlobal({
|
||||||
|
...global,
|
||||||
|
activeSessions: {
|
||||||
|
byHash: newSessions,
|
||||||
|
orderedHashes: global.activeSessions.orderedHashes.filter((el) => el !== hash),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('terminateAllAuthorizations', async (global) => {
|
||||||
|
const result = await callApi('terminateAllAuthorizations');
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const currentSessionHash = global.activeSessions.orderedHashes
|
||||||
|
.find((hash) => global.activeSessions.byHash[hash].isCurrent);
|
||||||
|
if (!currentSessionHash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentSession = global.activeSessions.byHash[currentSessionHash];
|
||||||
|
|
||||||
|
setGlobal({
|
||||||
|
...global,
|
||||||
|
activeSessions: {
|
||||||
|
byHash: {
|
||||||
|
[currentSessionHash]: currentSession,
|
||||||
|
},
|
||||||
|
orderedHashes: [currentSessionHash],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('changeSessionSettings', async (global, actions, payload) => {
|
||||||
|
const { hash, areCallsEnabled } = payload;
|
||||||
|
const result = await callApi('changeSessionSettings', {
|
||||||
|
hash,
|
||||||
|
areCallsEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
setGlobal({
|
||||||
|
...global,
|
||||||
|
activeSessions: {
|
||||||
|
...global.activeSessions,
|
||||||
|
byHash: {
|
||||||
|
...global.activeSessions.byHash,
|
||||||
|
[hash]: {
|
||||||
|
...global.activeSessions.byHash[hash],
|
||||||
|
areCallsEnabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('changeSessionTtl', async (global, actions, payload) => {
|
||||||
|
const { days } = payload;
|
||||||
|
|
||||||
|
const result = await callApi('changeSessionTtl', { days });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
setGlobal({
|
||||||
|
...global,
|
||||||
|
activeSessions: {
|
||||||
|
...global.activeSessions,
|
||||||
|
ttlDays: days,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -241,48 +241,6 @@ addActionHandler('unblockContact', async (global, actions, payload) => {
|
|||||||
setGlobal(removeBlockedContact(getGlobal(), contactId));
|
setGlobal(removeBlockedContact(getGlobal(), contactId));
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadAuthorizations', async () => {
|
|
||||||
const result = await callApi('fetchAuthorizations');
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setGlobal({
|
|
||||||
...getGlobal(),
|
|
||||||
activeSessions: result,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('terminateAuthorization', async (global, actions, payload) => {
|
|
||||||
const { hash } = payload!;
|
|
||||||
|
|
||||||
const result = await callApi('terminateAuthorization', hash);
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
global = getGlobal();
|
|
||||||
|
|
||||||
setGlobal({
|
|
||||||
...global,
|
|
||||||
activeSessions: global.activeSessions.filter((session) => session.hash !== hash),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('terminateAllAuthorizations', async (global) => {
|
|
||||||
const result = await callApi('terminateAllAuthorizations');
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
global = getGlobal();
|
|
||||||
|
|
||||||
setGlobal({
|
|
||||||
...global,
|
|
||||||
activeSessions: global.activeSessions.filter((session) => session.isCurrent),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('loadNotificationExceptions', async (global) => {
|
addActionHandler('loadNotificationExceptions', async (global) => {
|
||||||
const { serverTimeOffset } = global;
|
const { serverTimeOffset } = global;
|
||||||
|
|
||||||
|
|||||||
@ -235,6 +235,13 @@ function migrateCache(cached: GlobalState, initialState: GlobalState) {
|
|||||||
if (!cached.trustedBotIds) {
|
if (!cached.trustedBotIds) {
|
||||||
cached.trustedBotIds = [];
|
cached.trustedBotIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cached.activeSessions?.byHash === undefined) {
|
||||||
|
cached.activeSessions = {
|
||||||
|
byHash: {},
|
||||||
|
orderedHashes: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCache() {
|
function updateCache() {
|
||||||
|
|||||||
@ -138,7 +138,10 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
|
|
||||||
dialogs: [],
|
dialogs: [],
|
||||||
|
|
||||||
activeSessions: [],
|
activeSessions: {
|
||||||
|
byHash: {},
|
||||||
|
orderedHashes: [],
|
||||||
|
},
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
byKey: {
|
byKey: {
|
||||||
|
|||||||
@ -482,8 +482,11 @@ export type GlobalState = {
|
|||||||
notifications: ApiNotification[];
|
notifications: ApiNotification[];
|
||||||
dialogs: (ApiError | ApiInviteInfo)[];
|
dialogs: (ApiError | ApiInviteInfo)[];
|
||||||
|
|
||||||
// TODO Move to settings
|
activeSessions: {
|
||||||
activeSessions: ApiSession[];
|
byHash: Record<string, ApiSession>;
|
||||||
|
orderedHashes: string[];
|
||||||
|
ttlDays?: number;
|
||||||
|
};
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
byKey: ISettings;
|
byKey: ISettings;
|
||||||
@ -598,6 +601,13 @@ export interface ActionPayloads {
|
|||||||
description: string;
|
description: string;
|
||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
};
|
};
|
||||||
|
changeSessionSettings: {
|
||||||
|
hash: string;
|
||||||
|
areCallsEnabled: boolean;
|
||||||
|
};
|
||||||
|
changeSessionTtl: {
|
||||||
|
days: number;
|
||||||
|
};
|
||||||
|
|
||||||
// Chats
|
// Chats
|
||||||
openChat: {
|
openChat: {
|
||||||
@ -700,7 +710,6 @@ export interface ActionPayloads {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Bots
|
// Bots
|
||||||
|
|
||||||
clickBotInlineButton: {
|
clickBotInlineButton: {
|
||||||
messageId: number;
|
messageId: number;
|
||||||
button: ApiKeyboardButton;
|
button: ApiKeyboardButton;
|
||||||
@ -778,7 +787,6 @@ export interface ActionPayloads {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
|
|
||||||
openPollModal: {
|
openPollModal: {
|
||||||
isQuiz?: boolean;
|
isQuiz?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1032,6 +1032,8 @@ account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPa
|
|||||||
account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
|
account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
|
||||||
account.getContentSettings#8b9b4dae = account.ContentSettings;
|
account.getContentSettings#8b9b4dae = account.ContentSettings;
|
||||||
account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool;
|
account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool;
|
||||||
|
account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool;
|
||||||
|
account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool;
|
||||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||||
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
|
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
|
||||||
contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;
|
contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;
|
||||||
|
|||||||
@ -45,6 +45,8 @@
|
|||||||
"account.getContentSettings",
|
"account.getContentSettings",
|
||||||
"account.reportPeer",
|
"account.reportPeer",
|
||||||
"account.reportProfilePhoto",
|
"account.reportProfilePhoto",
|
||||||
|
"account.changeAuthorizationSettings",
|
||||||
|
"account.setAuthorizationTTL",
|
||||||
"users.getUsers",
|
"users.getUsers",
|
||||||
"users.getFullUser",
|
"users.getFullUser",
|
||||||
"contacts.getContacts",
|
"contacts.getContacts",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user