Monetization Statistics: Implement verification modal for withdraw button (#4929)
This commit is contained in:
parent
505f67674a
commit
09203ae76e
@ -77,6 +77,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
||||
group_transcribe_level_min?: number;
|
||||
new_noncontact_peers_require_premium_without_ownpremium?: boolean;
|
||||
channel_restrict_sponsored_level_min?: number;
|
||||
channel_revenue_withdrawal_enabled?: boolean;
|
||||
// Upload premium notifications
|
||||
upload_premium_speedup_notify_period?: number;
|
||||
upload_premium_speedup_download?: number;
|
||||
@ -161,5 +162,6 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
||||
bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload,
|
||||
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
|
||||
channelRestrictAdsLevelMin: appConfig.channel_restrict_sponsored_level_min,
|
||||
isChannelRevenueWithdrawalEnabled: appConfig.channel_revenue_withdrawal_enabled,
|
||||
};
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import {
|
||||
Api as GramJs,
|
||||
sessions,
|
||||
} from '../../../lib/gramjs';
|
||||
import type { TwoFaParams } from '../../../lib/gramjs/client/2fa';
|
||||
import type { TwoFaParams, TwoFaPasswordParams } from '../../../lib/gramjs/client/2fa';
|
||||
import TelegramClient from '../../../lib/gramjs/client/TelegramClient';
|
||||
import { Logger as GramJsLogger } from '../../../lib/gramjs/extensions/index';
|
||||
|
||||
@ -369,6 +369,10 @@ export function getTmpPassword(currentPassword: string, ttl?: number) {
|
||||
return client.getTmpPassword(currentPassword, ttl);
|
||||
}
|
||||
|
||||
export function getCurrentPassword(params: TwoFaPasswordParams) {
|
||||
return client.getCurrentPassword(params);
|
||||
}
|
||||
|
||||
export function abortChatRequests(params: { chatId: string; threadId?: ThreadId }) {
|
||||
const { chatId, threadId } = params;
|
||||
const controller = CHAT_ABORT_CONTROLLERS.get(chatId);
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
} from '../apiBuilders/statistics';
|
||||
import { buildInputEntity, buildInputPeer } from '../gramjsBuilders';
|
||||
import { invokeRequest } from './client';
|
||||
import { getPassword, onPasswordError } from './twoFaSettings';
|
||||
|
||||
export async function fetchChannelStatistics({
|
||||
chat, dcId,
|
||||
@ -211,3 +212,32 @@ export async function fetchStoryPublicForwards({
|
||||
nextOffset: result.nextOffset,
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadMonetizationRevenueWithdrawalUrl({
|
||||
chat, currentPassword,
|
||||
}: { chat: ApiChat; currentPassword: string }) {
|
||||
try {
|
||||
const password = await getPassword(currentPassword);
|
||||
|
||||
if (!password || 'error' in password) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = await invokeRequest(new GramJs.stats.GetBroadcastRevenueWithdrawalUrl({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
password,
|
||||
}), {
|
||||
shouldThrow: true,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err: any) {
|
||||
onPasswordError(err);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -2,7 +2,9 @@ import { Api as GramJs, errors } from '../../../lib/gramjs';
|
||||
|
||||
import { DEBUG } from '../../../config';
|
||||
import { sendApiUpdate } from '../updates/apiUpdateEmitter';
|
||||
import { getTmpPassword, invokeRequest, updateTwoFaSettings } from './client';
|
||||
import {
|
||||
getCurrentPassword, getTmpPassword, invokeRequest, updateTwoFaSettings,
|
||||
} from './client';
|
||||
|
||||
const ApiErrors: { [k: string]: string } = {
|
||||
EMAIL_UNCONFIRMED: 'Email unconfirmed',
|
||||
@ -11,6 +13,7 @@ const ApiErrors: { [k: string]: string } = {
|
||||
NEW_SETTINGS_INVALID: 'The new password settings are invalid',
|
||||
CODE_INVALID: 'Invalid Code',
|
||||
PASSWORD_HASH_INVALID: 'Invalid Password',
|
||||
PASSWORD_MISSING: 'You must enable 2FA before executing this operation',
|
||||
};
|
||||
|
||||
const emailCodeController: {
|
||||
@ -45,6 +48,18 @@ export function getTemporaryPaymentPassword(password: string, ttl?: number) {
|
||||
return getTmpPassword(password, ttl);
|
||||
}
|
||||
|
||||
export function getPassword(password: string) {
|
||||
try {
|
||||
return getCurrentPassword({
|
||||
currentPassword: password,
|
||||
onPasswordCodeError: onPasswordError,
|
||||
});
|
||||
} catch (err: any) {
|
||||
onPasswordError(err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkPassword(currentPassword: string) {
|
||||
try {
|
||||
await updateTwoFaSettings({ isCheckPassword: true, currentPassword });
|
||||
@ -134,3 +149,28 @@ function onError(err: Error) {
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
export function onPasswordError(err: Error) {
|
||||
let message: string;
|
||||
|
||||
if (err instanceof errors.PasswordModifiedError) {
|
||||
const hours = Math.ceil(Number(err.seconds) / 60 / 60);
|
||||
message = `Too many attempts. Try again in ${hours > 1 ? `${hours} hours` : 'an hour'}`;
|
||||
} else {
|
||||
message = ApiErrors[err.message];
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = 'Unexpected Error';
|
||||
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
sendApiUpdate({
|
||||
'@type': 'updatePasswordError',
|
||||
error: message,
|
||||
});
|
||||
}
|
||||
|
||||
@ -210,6 +210,7 @@ export interface ApiAppConfig {
|
||||
bandwidthPremiumUploadSpeedup?: number;
|
||||
bandwidthPremiumDownloadSpeedup?: number;
|
||||
channelRestrictAdsLevelMin?: number;
|
||||
isChannelRevenueWithdrawalEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiConfig {
|
||||
|
||||
@ -481,6 +481,11 @@ export type ApiUpdateTwoFaError = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type ApiUpdatePasswordError = {
|
||||
'@type': 'updatePasswordError';
|
||||
error: string;
|
||||
};
|
||||
|
||||
export type ApiUpdateNotifySettings = {
|
||||
'@type': 'updateNotifySettings';
|
||||
peerType: 'contact' | 'group' | 'broadcast';
|
||||
@ -776,7 +781,7 @@ export type ApiUpdate = (
|
||||
ApiUpdateRecentStickers | ApiUpdateSavedGifs | ApiUpdateNewScheduledMessage | ApiUpdateMoveStickerSetToTop |
|
||||
ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage |
|
||||
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateMessageTranslations |
|
||||
ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent |
|
||||
ApiUpdateTwoFaError | ApiUpdatePasswordError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent |
|
||||
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
|
||||
ApiUpdateServerTimeOffset | ApiUpdateShowInvite | ApiUpdateMessageReactions | ApiUpdateSavedReactionTags |
|
||||
ApiUpdateGroupCallParticipants | ApiUpdateGroupCallConnection | ApiUpdateGroupCall | ApiUpdateGroupCallStreams |
|
||||
|
||||
@ -32,6 +32,7 @@ export { default as StarsTransactionInfoModal } from '../components/modals/stars
|
||||
|
||||
export { default as AboutAdsModal } from '../components/common/AboutAdsModal';
|
||||
export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal';
|
||||
export { default as VerificationMonetizationModal } from '../components/common/VerificationMonetizationModal';
|
||||
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
||||
export { default as CalendarModal } from '../components/common/CalendarModal';
|
||||
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React from '../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './VerificationMonetizationModal';
|
||||
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const VerificationMonetizationModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const VerificationMonetizationModal = useModuleLoader(Bundles.Extra, 'VerificationMonetizationModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return VerificationMonetizationModal ? <VerificationMonetizationModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default VerificationMonetizationModalAsync;
|
||||
@ -0,0 +1,9 @@
|
||||
.root {
|
||||
:global(.modal-dialog) {
|
||||
max-width: 25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
87
src/components/common/VerificationMonetizationModal.tsx
Normal file
87
src/components/common/VerificationMonetizationModal.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo,
|
||||
useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import Modal from '../ui/Modal';
|
||||
import PasswordForm from './PasswordForm';
|
||||
|
||||
import styles from './VerificationMonetizationModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
onClose: NoneToVoidFunction;
|
||||
chatId: string;
|
||||
passwordHint?: string;
|
||||
error?: string;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const VerificationMonetizationModal: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
chatId,
|
||||
onClose,
|
||||
passwordHint,
|
||||
error,
|
||||
isLoading,
|
||||
}) => {
|
||||
const {
|
||||
clearMonetizationInfo, loadMonetizationRevenueWithdrawalUrl,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
|
||||
const [shouldShowPassword, setShouldShowPassword] = useState(false);
|
||||
|
||||
const handleSubmit = useLastCallback((password: string) => {
|
||||
loadMonetizationRevenueWithdrawalUrl({
|
||||
chatId,
|
||||
currentPassword: password,
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const handleClearError = useLastCallback(() => {
|
||||
clearMonetizationInfo();
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
hasCloseButton
|
||||
title={lang('EnterPassword')}
|
||||
className={styles.root}
|
||||
contentClassName={styles.content}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={buildClassName(styles.content, 'settings-content password-form custom-scroll')}>
|
||||
<div className="settings-item pt-0">
|
||||
<PasswordForm
|
||||
shouldShowSubmit
|
||||
placeholder={lang('Password')}
|
||||
error={error && lang(error)}
|
||||
description={lang('Channel.OwnershipTransfer.EnterPasswordText')}
|
||||
clearError={handleClearError}
|
||||
isLoading={isLoading}
|
||||
hint={passwordHint}
|
||||
isPasswordVisible={shouldShowPassword}
|
||||
shouldResetValue={isOpen}
|
||||
onChangePasswordVisibility={setShouldShowPassword}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(VerificationMonetizationModal);
|
||||
@ -5,19 +5,22 @@ import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChannelMonetizationStatistics, StatisticsGraph } from '../../../api/types';
|
||||
|
||||
import { FRAGMENT_ADS_URL } from '../../../config';
|
||||
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import AboutMonetizationModal from '../../common/AboutMonetizationModal.async';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import VerificationMonetizationModal from '../../common/VerificationMonetizationModal.async';
|
||||
import Button from '../../ui/Button';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
import Link from '../../ui/Link';
|
||||
import Loading from '../../ui/Loading';
|
||||
import StatisticsOverview from './StatisticsOverview';
|
||||
@ -47,29 +50,46 @@ type StateProps = {
|
||||
chatId: string;
|
||||
dcId?: number;
|
||||
statistics?: ApiChannelMonetizationStatistics;
|
||||
canCollect?: boolean;
|
||||
isCreator?: boolean;
|
||||
isChannelRevenueWithdrawalEnabled?: boolean;
|
||||
hasPassword?: boolean;
|
||||
passwordHint?: string;
|
||||
error?: string;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const MonetizationStatistics = ({
|
||||
chatId,
|
||||
dcId,
|
||||
statistics,
|
||||
canCollect,
|
||||
isCreator,
|
||||
isChannelRevenueWithdrawalEnabled,
|
||||
hasPassword,
|
||||
passwordHint,
|
||||
error,
|
||||
isLoading,
|
||||
}: StateProps) => {
|
||||
const { loadChannelMonetizationStatistics } = getActions();
|
||||
const { loadChannelMonetizationStatistics, loadPasswordInfo } = getActions();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const loadedCharts = useRef<string[]>([]);
|
||||
const forceUpdate = useForceUpdate();
|
||||
const [isAboutMonetizationModalOpen, openAboutMonetizationModal, closeAboutMonetizationModal] = useFlag(false);
|
||||
const hasAvailableBalance = Boolean(statistics?.balances?.availableBalance !== 0);
|
||||
const [
|
||||
isVerificationMonetizationModalOpen, openVerificationMonetizationModal, closeVerificationMonetizationModal,
|
||||
] = useFlag(false);
|
||||
const [isConfirmPasswordDialogOpen, openConfirmPasswordDialogOpen, closeConfirmPasswordDialogOpen] = useFlag();
|
||||
const availableBalance = statistics?.balances?.availableBalance;
|
||||
const canWithdraw = isCreator && isChannelRevenueWithdrawalEnabled && Boolean(availableBalance);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatId) {
|
||||
loadChannelMonetizationStatistics({ chatId });
|
||||
loadPasswordInfo();
|
||||
}
|
||||
}, [chatId, loadChannelMonetizationStatistics]);
|
||||
|
||||
@ -86,7 +106,7 @@ const MonetizationStatistics = ({
|
||||
return;
|
||||
}
|
||||
|
||||
MONETIZATION_GRAPHS.forEach((name, index: number) => {
|
||||
MONETIZATION_GRAPHS.filter(Boolean).forEach((name, index: number) => {
|
||||
const graph = statistics[name as keyof typeof statistics];
|
||||
const isAsync = typeof graph === 'string';
|
||||
|
||||
@ -115,7 +135,6 @@ const MonetizationStatistics = ({
|
||||
}, [isReady, statistics, oldLang, chatId, dcId, forceUpdate]);
|
||||
|
||||
function renderAvailableReward() {
|
||||
const availableBalance = statistics?.balances?.availableBalance;
|
||||
const [integerTonPart, decimalTonPart] = availableBalance ? availableBalance.toFixed(4).split('.') : [0];
|
||||
const [integerUsdPart, decimalUsdPart] = availableBalance
|
||||
&& statistics?.usdRate ? (availableBalance * statistics.usdRate).toFixed(2).split('.') : [0];
|
||||
@ -172,6 +191,14 @@ const MonetizationStatistics = ({
|
||||
);
|
||||
}, [lang, oldLang]);
|
||||
|
||||
const verificationMonetizationHandler = useLastCallback(() => {
|
||||
if (hasPassword) {
|
||||
openVerificationMonetizationModal();
|
||||
} else {
|
||||
openConfirmPasswordDialogOpen();
|
||||
}
|
||||
});
|
||||
|
||||
if (!isReady || !statistics) {
|
||||
return <Loading />;
|
||||
}
|
||||
@ -190,29 +217,49 @@ const MonetizationStatistics = ({
|
||||
{!loadedCharts.current.length && <Loading />}
|
||||
|
||||
<div ref={containerRef} className={styles.section}>
|
||||
{MONETIZATION_GRAPHS.map((graph) => (
|
||||
{MONETIZATION_GRAPHS.filter(Boolean).map((graph) => (
|
||||
<div key={graph} className={buildClassName(styles.graph, styles.hidden)} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{hasAvailableBalance && (
|
||||
<div className={styles.section}>
|
||||
{oldLang('lng_channel_earn_balance_title')}
|
||||
<div className={styles.section}>
|
||||
{oldLang('lng_channel_earn_balance_title')}
|
||||
|
||||
{renderAvailableReward()}
|
||||
{renderAvailableReward()}
|
||||
|
||||
<Button size="smaller" type="button" href={FRAGMENT_ADS_URL} disabled={canCollect && hasAvailableBalance}>
|
||||
{oldLang('MonetizationWithdraw')}
|
||||
</Button>
|
||||
<Button
|
||||
size="smaller"
|
||||
type="button"
|
||||
onClick={verificationMonetizationHandler}
|
||||
disabled={!canWithdraw}
|
||||
>
|
||||
{oldLang('MonetizationWithdraw')}
|
||||
</Button>
|
||||
|
||||
<div className={styles.textBottom}>{rewardsText}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.textBottom}>{rewardsText}</div>
|
||||
</div>
|
||||
|
||||
<AboutMonetizationModal
|
||||
isOpen={isAboutMonetizationModalOpen}
|
||||
onClose={closeAboutMonetizationModal}
|
||||
/>
|
||||
<VerificationMonetizationModal
|
||||
chatId={chatId}
|
||||
isOpen={isVerificationMonetizationModalOpen}
|
||||
onClose={closeVerificationMonetizationModal}
|
||||
passwordHint={passwordHint}
|
||||
error={error}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
isOnlyConfirm
|
||||
isOpen={isConfirmPasswordDialogOpen}
|
||||
onClose={closeConfirmPasswordDialogOpen}
|
||||
confirmHandler={closeConfirmPasswordDialogOpen}
|
||||
confirmLabel={lang('OK')}
|
||||
>
|
||||
<p>{renderText(oldLang('Monetization.Withdraw.Error.Text'), ['br'])}</p>
|
||||
</ConfirmDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -220,18 +267,38 @@ const MonetizationStatistics = ({
|
||||
export default memo(withGlobal(
|
||||
(global): StateProps => {
|
||||
const tabState = selectTabState(global);
|
||||
const {
|
||||
settings: {
|
||||
byKey: {
|
||||
hasPassword,
|
||||
},
|
||||
},
|
||||
twoFaSettings: {
|
||||
hint: passwordHint,
|
||||
},
|
||||
} = global;
|
||||
const isLoading = global.monetizationInfo?.isLoading;
|
||||
const error = global.monetizationInfo?.error;
|
||||
const monetizationStatistics = tabState.monetizationStatistics;
|
||||
const chatId = monetizationStatistics && monetizationStatistics.chatId;
|
||||
const chat = chatId ? selectChat(global, chatId) : undefined;
|
||||
const canCollect = chat && chat.isCreator;
|
||||
const dcId = selectChatFullInfo(global, chatId!)?.statisticsDcId;
|
||||
const isCreator = Boolean(chat?.isCreator);
|
||||
|
||||
const statistics = tabState.statistics.monetization;
|
||||
|
||||
const isChannelRevenueWithdrawalEnabled = global.appConfig?.isChannelRevenueWithdrawalEnabled;
|
||||
|
||||
return {
|
||||
chatId: chatId!,
|
||||
dcId,
|
||||
statistics,
|
||||
canCollect,
|
||||
isCreator,
|
||||
isChannelRevenueWithdrawalEnabled,
|
||||
hasPassword,
|
||||
passwordHint,
|
||||
error,
|
||||
isLoading,
|
||||
};
|
||||
},
|
||||
)(MonetizationStatistics));
|
||||
|
||||
@ -38,5 +38,6 @@ import './apiUpdaters/symbols';
|
||||
import './apiUpdaters/misc';
|
||||
import './apiUpdaters/settings';
|
||||
import './apiUpdaters/twoFaSettings';
|
||||
import './apiUpdaters/password';
|
||||
import './apiUpdaters/calls';
|
||||
import './apiUpdaters/payments';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { ActionReturnType } from '../../types';
|
||||
|
||||
import { areDeepEqual } from '../../../util/areDeepEqual';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
@ -5,6 +7,7 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import {
|
||||
updateChannelMonetizationStatistics,
|
||||
updateMessageStatistics,
|
||||
updateMonetizationInfo,
|
||||
updateStatistics,
|
||||
updateStatisticsGraph,
|
||||
updateStoryStatistics,
|
||||
@ -224,3 +227,41 @@ addActionHandler('loadStoryPublicForwards', async (global, actions, payload): Pr
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadMonetizationRevenueWithdrawalUrl', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
chatId, currentPassword, onSuccess, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
global = updateMonetizationInfo(global, { isLoading: true, error: undefined });
|
||||
setGlobal(global);
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await callApi('loadMonetizationRevenueWithdrawalUrl', { chat, currentPassword });
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = updateMonetizationInfo(global, { isLoading: false });
|
||||
setGlobal(global);
|
||||
|
||||
if (result) {
|
||||
onSuccess();
|
||||
actions.openUrl({
|
||||
url: result.url,
|
||||
shouldSkipModal: true,
|
||||
tabId,
|
||||
ignoreDeepLinks: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('clearMonetizationInfo', (global): ActionReturnType => {
|
||||
return updateMonetizationInfo(global, { error: undefined });
|
||||
});
|
||||
|
||||
20
src/global/actions/apiUpdaters/password.ts
Normal file
20
src/global/actions/apiUpdaters/password.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { ActionReturnType } from '../../types';
|
||||
|
||||
import { addActionHandler } from '../../index';
|
||||
|
||||
addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
switch (update['@type']) {
|
||||
case 'updatePasswordError': {
|
||||
return {
|
||||
...global,
|
||||
monetizationInfo: {
|
||||
...global.monetizationInfo,
|
||||
isLoading: false,
|
||||
error: update.error,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
@ -297,6 +297,8 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
||||
isMinimized: false,
|
||||
isHidden: false,
|
||||
},
|
||||
|
||||
monetizationInfo: {},
|
||||
};
|
||||
|
||||
export const INITIAL_TAB_STATE: TabState = {
|
||||
|
||||
@ -12,4 +12,5 @@ export * from './payments';
|
||||
export * from './statistics';
|
||||
export * from './stories';
|
||||
export * from './translations';
|
||||
export * from './password';
|
||||
export * from './general';
|
||||
|
||||
14
src/global/reducers/password.ts
Normal file
14
src/global/reducers/password.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import type { GlobalState } from '../types';
|
||||
|
||||
export function updateMonetizationInfo<T extends GlobalState>(
|
||||
global: T,
|
||||
update: GlobalState['monetizationInfo'],
|
||||
): T {
|
||||
return {
|
||||
...global,
|
||||
monetizationInfo: {
|
||||
...global.monetizationInfo,
|
||||
...update,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -872,6 +872,11 @@ export type GlobalState = {
|
||||
waitingEmailCodeLength?: number;
|
||||
};
|
||||
|
||||
monetizationInfo: {
|
||||
isLoading?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
attachmentSettings: {
|
||||
shouldCompress: boolean;
|
||||
shouldSendGrouped: boolean;
|
||||
@ -1285,6 +1290,7 @@ export interface ActionPayloads {
|
||||
updatePerformanceSettings: Partial<PerformanceType>;
|
||||
loadPasswordInfo: undefined;
|
||||
clearTwoFaError: undefined;
|
||||
clearMonetizationInfo: undefined;
|
||||
updatePassword: {
|
||||
currentPassword: string;
|
||||
password: string;
|
||||
@ -1825,6 +1831,12 @@ export interface ActionPayloads {
|
||||
chatId: string;
|
||||
} & WithTabId;
|
||||
|
||||
loadMonetizationRevenueWithdrawalUrl: {
|
||||
chatId: string;
|
||||
currentPassword: string;
|
||||
onSuccess: VoidFunction;
|
||||
} & WithTabId;
|
||||
|
||||
// ui
|
||||
dismissDialog: WithTabId | undefined;
|
||||
setNewChatMembersDialogState: {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import type TelegramClient from './TelegramClient';
|
||||
|
||||
import errors from '../errors';
|
||||
// eslint-disable-next-line import/no-named-default
|
||||
import { default as Api } from '../tl/api';
|
||||
|
||||
import { generateRandomBytes } from '../Helpers';
|
||||
import { computeCheck, computeDigest } from '../Password';
|
||||
import errors from '../errors';
|
||||
|
||||
export interface TwoFaParams {
|
||||
isCheckPassword?: boolean;
|
||||
@ -15,7 +17,13 @@ export interface TwoFaParams {
|
||||
onEmailCodeError?: (err: Error) => void;
|
||||
}
|
||||
|
||||
export interface TwoFaPasswordParams {
|
||||
currentPassword?: string;
|
||||
onPasswordCodeError?: (err: Error) => void;
|
||||
}
|
||||
|
||||
export type TmpPasswordResult = Api.account.TmpPassword | { error: string } | undefined;
|
||||
export type PasswordResult = Api.account.Password | { error: string } | undefined;
|
||||
|
||||
/**
|
||||
* Changes the 2FA settings of the logged in user.
|
||||
@ -147,3 +155,29 @@ export async function getTmpPassword(client: TelegramClient, currentPassword: st
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentPassword(
|
||||
client: TelegramClient,
|
||||
{
|
||||
currentPassword,
|
||||
onPasswordCodeError,
|
||||
}: TwoFaPasswordParams,
|
||||
) {
|
||||
const pwd = await client.invoke(new Api.account.GetPassword());
|
||||
|
||||
if (!pwd) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return currentPassword ? await computeCheck(pwd, currentPassword!) : new Api.InputCheckPasswordEmpty();
|
||||
} catch (err: any) {
|
||||
if (err instanceof errors.PasswordModifiedError) {
|
||||
return onPasswordCodeError!(err);
|
||||
} else if (err.message === 'PASSWORD_HASH_INVALID') {
|
||||
return { error: err.message };
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
src/lib/gramjs/client/TelegramClient.d.ts
vendored
6
src/lib/gramjs/client/TelegramClient.d.ts
vendored
@ -1,4 +1,6 @@
|
||||
import type { TmpPasswordResult, TwoFaParams, updateTwoFaSettings } from './2fa';
|
||||
import type {
|
||||
PasswordResult, TmpPasswordResult, TwoFaParams, TwoFaPasswordParams, updateTwoFaSettings,
|
||||
} from './2fa';
|
||||
import type { BotAuthParams, UserAuthParams } from './auth';
|
||||
import type { downloadFile, DownloadFileParams } from './downloadFile';
|
||||
import type { uploadFile, UploadFileParams } from './uploadFile';
|
||||
@ -24,6 +26,8 @@ declare class TelegramClient {
|
||||
|
||||
async getTmpPassword(currentPassword: string, ttl?: number): Promise<TmpPasswordResult>;
|
||||
|
||||
async getCurrentPassword(Params: TwoFaPasswordParams): Promise<PasswordResult>;
|
||||
|
||||
setPingCallback(callback: () => Promise<void>);
|
||||
|
||||
setForceHttpTransport: (forceHttpTransport: boolean) => void;
|
||||
|
||||
@ -26,6 +26,7 @@ const { uploadFile } = require('./uploadFile');
|
||||
const {
|
||||
updateTwoFaSettings,
|
||||
getTmpPassword,
|
||||
getCurrentPassword,
|
||||
} = require('./2fa');
|
||||
const RequestState = require('../network/RequestState');
|
||||
const Deferred = require('../../../util/Deferred').default;
|
||||
@ -1118,6 +1119,10 @@ class TelegramClient {
|
||||
return getTmpPassword(this, currentPassword, ttl);
|
||||
}
|
||||
|
||||
getCurrentPassword(currentPassword) {
|
||||
return getCurrentPassword(this, currentPassword);
|
||||
}
|
||||
|
||||
// event region
|
||||
addEventHandler(callback, event) {
|
||||
this._eventBuilders.push([event, callback]);
|
||||
|
||||
@ -101,6 +101,16 @@ class EmailUnconfirmedError extends BadRequestError {
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordModifiedError extends BadRequestError {
|
||||
constructor(args) {
|
||||
const seconds = Number(args.capture || 0);
|
||||
super(`The password was modified less than 24 hours ago, try again in ${seconds} seconds.`);
|
||||
// eslint-disable-next-line max-len
|
||||
this.message = `The password was modified less than 24 hours ago, try again in ${seconds} seconds.`;
|
||||
this.seconds = seconds;
|
||||
}
|
||||
}
|
||||
|
||||
const rpcErrorRe = [
|
||||
[/FILE_MIGRATE_(\d+)/, FileMigrateError],
|
||||
[/FLOOD_TEST_PHONE_WAIT_(\d+)/, FloodTestPhoneWaitError],
|
||||
@ -112,6 +122,7 @@ const rpcErrorRe = [
|
||||
[/USER_MIGRATE_(\d+)/, UserMigrateError],
|
||||
[/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError],
|
||||
[/EMAIL_UNCONFIRMED_(\d+)/, EmailUnconfirmedError],
|
||||
[/PASSWORD_TOO_FRESH_(\d+)/, PasswordModifiedError],
|
||||
[/^Timeout$/, TimedOutError],
|
||||
];
|
||||
module.exports = {
|
||||
@ -126,4 +137,5 @@ module.exports = {
|
||||
NetworkMigrateError,
|
||||
MsgWaitError,
|
||||
EmailUnconfirmedError,
|
||||
PasswordModifiedError,
|
||||
};
|
||||
|
||||
@ -1624,8 +1624,8 @@ bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:fla
|
||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||
bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>;
|
||||
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
||||
bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>;
|
||||
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||
@ -1676,6 +1676,7 @@ stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel ms
|
||||
stats.getStoryStats#374fef40 flags:# dark:flags.0?true peer:InputPeer id:int = stats.StoryStats;
|
||||
stats.getStoryPublicForwards#a6437ef6 peer:InputPeer id:int offset:string limit:int = stats.PublicForwards;
|
||||
stats.getBroadcastRevenueStats#75dfb671 flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastRevenueStats;
|
||||
stats.getBroadcastRevenueWithdrawalUrl#2a65ef73 channel:InputChannel password:InputCheckPasswordSRP = stats.BroadcastRevenueWithdrawalUrl;
|
||||
chatlists.exportChatlistInvite#8472478e chatlist:InputChatlist title:string peers:Vector<InputPeer> = chatlists.ExportedChatlistInvite;
|
||||
chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool;
|
||||
chatlists.editExportedInvite#653db63d flags:# chatlist:InputChatlist slug:string title:flags.1?string peers:flags.2?Vector<InputPeer> = ExportedChatlistInvite;
|
||||
|
||||
@ -282,6 +282,7 @@
|
||||
"stats.getStoryStats",
|
||||
"stats.getStoryPublicForwards",
|
||||
"stats.loadAsyncGraph",
|
||||
"stats.getBroadcastRevenueWithdrawalUrl",
|
||||
"messages.getAttachMenuBots",
|
||||
"messages.getAttachMenuBot",
|
||||
"messages.toggleBotInAttachMenu",
|
||||
|
||||
@ -20,6 +20,7 @@ const READABLE_ERROR_MESSAGES: Record<string, string> = {
|
||||
MEDIA_NEW_INVALID: 'Failed to replace new media',
|
||||
MESSAGE_NOT_MODIFIED: 'Message not modified. The new content is identical to the current one.',
|
||||
MEDIA_INVALID: 'Media invalid',
|
||||
PASSWORD_HASH_INVALID: 'Incorrect password',
|
||||
PHOTO_EXT_INVALID: 'The extension of the photo is invalid',
|
||||
PHOTO_INVALID_DIMENSIONS: 'The photo dimensions are invalid',
|
||||
PHOTO_SAVE_FILE_INVALID: 'Internal issues, try again later',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user