mirror of
https://github.com/lux-tel/session-desktop.git
synced 2026-10-30 20:33:24 +00:00
824 lines
27 KiB
TypeScript
824 lines
27 KiB
TypeScript
import useAsync from 'react-use/lib/useAsync';
|
|
import { ipcRenderer, shell } from 'electron';
|
|
import { useCallback, useState } from 'react';
|
|
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
|
import useInterval from 'react-use/lib/useInterval';
|
|
import { filesize } from 'filesize';
|
|
import styled from 'styled-components';
|
|
|
|
import type { ProProof, PubkeyType } from 'libsession_util_nodejs';
|
|
import { chunk, toNumber } from 'lodash';
|
|
import { getAppDispatch } from '../../../state/dispatch';
|
|
import { Flex } from '../../basic/Flex';
|
|
import { SpacerXS } from '../../basic/Text';
|
|
import { tr } from '../../../localization/localeTools';
|
|
import { CopyToClipboardIcon } from '../../buttons';
|
|
import { Localizer } from '../../basic/Localizer';
|
|
import {
|
|
SessionButton,
|
|
SessionButtonColor,
|
|
SessionButtonProps,
|
|
SessionButtonShape,
|
|
} from '../../basic/SessionButton';
|
|
import { ToastUtils, UserUtils } from '../../../session/utils';
|
|
import { getLatestReleaseFromFileServer } from '../../../session/apis/file_server_api/FileServerApi';
|
|
import { SessionSpinner } from '../../loading';
|
|
import { updateDebugMenuModal } from '../../../state/ducks/modalDialog';
|
|
import { setDebugMode } from '../../../state/ducks/debug';
|
|
import LIBSESSION_CONSTANTS from '../../../session/utils/libsession/libsession_constants';
|
|
import { type ReleaseChannels } from '../../../updater/types';
|
|
import { fetchLatestRelease } from '../../../session/fetch_latest_release';
|
|
import { saveLogToDesktop } from '../../../util/logger/renderer_process_logging';
|
|
import { DURATION } from '../../../session/constants';
|
|
import { Errors } from '../../../types/Errors';
|
|
import { PubKey } from '../../../session/types';
|
|
import { ConvoHub } from '../../../session/conversations';
|
|
import { ConversationTypeEnum } from '../../../models/types';
|
|
import { ContactsWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface';
|
|
import { usePolling } from '../../../hooks/usePolling';
|
|
import { releasedFeaturesActions } from '../../../state/ducks/releasedFeatures';
|
|
import { networkDataActions } from '../../../state/ducks/networkData';
|
|
import { DEBUG_MENU_PAGE, DebugMenuSection, type DebugMenuPageProps } from './DebugMenuModal';
|
|
import { SimpleSessionInput } from '../../inputs/SessionInput';
|
|
import { NetworkTime } from '../../../util/NetworkTime';
|
|
import { SessionButtonShiny } from '../../basic/SessionButtonShiny';
|
|
import ProBackendAPI from '../../../session/apis/pro_backend_api/ProBackendAPI';
|
|
import { getProMasterKeyHex } from '../../../session/utils/User';
|
|
import { FlagToggle } from './FeatureFlags';
|
|
import { getFeatureFlag } from '../../../state/ducks/types/releasedFeaturesReduxTypes';
|
|
import { SessionDisplayNameOnlyPrivate } from '../../../models/profile';
|
|
import {
|
|
clearAllUrlInteractions,
|
|
getUrlInteractions,
|
|
removeUrlInteractionHistory,
|
|
urlInteractionToString,
|
|
} from '../../../util/urlHistory';
|
|
import { formatRoundedUpTimeUntilTimestamp } from '../../../util/i18n/formatting/generics';
|
|
import { LucideIcon } from '../../icon/LucideIcon';
|
|
import { LUCIDE_ICONS_UNICODE } from '../../icon/lucide';
|
|
import { getIsProAvailableMemo } from '../../../hooks/useIsProAvailable';
|
|
import { UserConfigWrapperActions } from '../../../webworker/workers/browser/libsession/libsession_worker_userconfig_interface';
|
|
import {
|
|
clearAllCtaInteractions,
|
|
getCtaInteractions,
|
|
removeCtaInteractionHistory,
|
|
} from '../../../util/ctaHistory';
|
|
import { CTAVariant } from '../cta/types';
|
|
|
|
type DebugButtonProps = SessionButtonProps & { shiny?: boolean; hide?: boolean };
|
|
|
|
export function DebugButton({ shiny = true, style: _style, hide, ...rest }: DebugButtonProps) {
|
|
if (hide) {
|
|
return null;
|
|
}
|
|
|
|
const style = { minWidth: 'max-content', width: '48%', ..._style };
|
|
|
|
if (shiny) {
|
|
return (
|
|
<SessionButtonShiny
|
|
shinyContainerStyle={style}
|
|
buttonColor={SessionButtonColor.PrimaryDark}
|
|
buttonShape={SessionButtonShape.Square}
|
|
{...rest}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return <SessionButton style={style} buttonShape={SessionButtonShape.Square} {...rest} />;
|
|
}
|
|
|
|
const hexRef = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
|
|
|
|
function genRandomHexString(length: number) {
|
|
const result = [];
|
|
|
|
for (let n = 0; n < length; n++) {
|
|
result.push(hexRef[Math.floor(Math.random() * 16)]);
|
|
}
|
|
return result.join('');
|
|
}
|
|
|
|
async function generateOneRandomContact() {
|
|
const numBytes = PubKey.PUBKEY_LEN - 2;
|
|
|
|
const hexBuffer = genRandomHexString(numBytes);
|
|
const id: PubkeyType = `05${hexBuffer}`;
|
|
const created = await ConvoHub.use().getOrCreateAndWait(id, ConversationTypeEnum.PRIVATE);
|
|
// now() is not going to be synced on devices, instead createdAt will be used.
|
|
// createdAt is set to now in libsession-util itself,
|
|
// but we still need to mark that conversation as active
|
|
// for it to be inserted in the config
|
|
created.setActiveAt(Date.now());
|
|
await created.setIsApproved(true, false);
|
|
const profile = new SessionDisplayNameOnlyPrivate({
|
|
convo: created,
|
|
displayName: id.slice(2, 8),
|
|
profileUpdatedAtSeconds: NetworkTime.nowSeconds(),
|
|
});
|
|
await profile.applyChangesIfNeeded();
|
|
|
|
await created.commit();
|
|
return created;
|
|
}
|
|
|
|
const CheckVersionButton = ({ channelToCheck }: { channelToCheck: ReleaseChannels }) => {
|
|
const [loading, setLoading] = useState(false);
|
|
const state = useAsync(async () => {
|
|
const userEd25519KeyPairBytes = await UserUtils.getUserED25519KeyPairBytes();
|
|
const userEd25519SecretKey = userEd25519KeyPairBytes?.privKeyBytes;
|
|
return userEd25519SecretKey;
|
|
});
|
|
|
|
return (
|
|
<DebugButton
|
|
onClick={async () => {
|
|
if (state.loading || state.error) {
|
|
window.log.error(
|
|
`[debugMenu] CheckVersionButton checking ${channelToCheck} channel state loading ${state.loading} error ${state.error}`
|
|
);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
if (!state.value) {
|
|
window.log.error(
|
|
`[debugMenu] CheckVersionButton checking ${channelToCheck} channel no userEd25519SecretKey`
|
|
);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
const result = await getLatestReleaseFromFileServer(state.value, channelToCheck);
|
|
if (!result) {
|
|
ToastUtils.pushToastError(
|
|
'CheckVersionButton',
|
|
`Failed to fetch ${channelToCheck} release`
|
|
);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
const [versionNumber, releaseChannel] = result;
|
|
if (!releaseChannel) {
|
|
ToastUtils.pushToastError(
|
|
'CheckVersionButton',
|
|
`Failed to return release channel when fetching`
|
|
);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
if (!versionNumber) {
|
|
ToastUtils.pushToastError(
|
|
'CheckVersionButton',
|
|
`Failed to fetch ${channelToCheck} release version`
|
|
);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
setLoading(false);
|
|
|
|
ToastUtils.pushToastInfo(`CheckVersionButtonAvailable`, `Available: v${versionNumber}`);
|
|
ToastUtils.pushToastInfo(
|
|
'CheckVersionButtonCurrent',
|
|
`Current: v${window.versionInfo.version}`
|
|
);
|
|
}}
|
|
>
|
|
<SessionSpinner $loading={loading || state.loading} $color={'var(--text-primary-color)'} />
|
|
{!loading && !state.loading ? `Check ${channelToCheck} version` : null}
|
|
</DebugButton>
|
|
);
|
|
};
|
|
|
|
const CheckForUpdatesButton = () => {
|
|
const [state, handleCheckForUpdates] = useAsyncFn(async () => {
|
|
window.log.warn(
|
|
'[updater] [debugMenu] CheckForUpdatesButton clicked! Current version',
|
|
window.getVersion()
|
|
);
|
|
|
|
try {
|
|
const userEd25519KeyPairBytes = await UserUtils.getUserED25519KeyPairBytes();
|
|
const userEd25519SecretKey = userEd25519KeyPairBytes?.privKeyBytes;
|
|
const newVersion = await fetchLatestRelease.fetchReleaseFromFSAndUpdateMain(
|
|
userEd25519SecretKey,
|
|
true
|
|
);
|
|
|
|
if (!newVersion) {
|
|
throw new Error('No version returned from fileserver');
|
|
}
|
|
|
|
const success = await ipcRenderer.invoke('force-update-check');
|
|
if (!success) {
|
|
ToastUtils.pushToastError('CheckForUpdatesButton', 'Check for updates failed! See logs');
|
|
}
|
|
} catch (error) {
|
|
window.log.error(
|
|
'[updater] [debugMenu] CheckForUpdatesButton',
|
|
error && error.stack ? error.stack : error
|
|
);
|
|
}
|
|
});
|
|
|
|
return (
|
|
<DebugButton
|
|
onClick={() => {
|
|
void handleCheckForUpdates();
|
|
}}
|
|
>
|
|
<SessionSpinner $loading={state.loading} $color={'var(--text-primary-color)'} />
|
|
{!state.loading ? 'Check for updates' : null}
|
|
</DebugButton>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Using a function here to avoid a useCallback below
|
|
*/
|
|
function fetchLogSizeFromIpc() {
|
|
return ipcRenderer.invoke('get-logs-folder-size');
|
|
}
|
|
|
|
const ClearOldLogsButton = () => {
|
|
const [logSize, setLogSize] = useState(0);
|
|
useInterval(async () => {
|
|
const fetched = await fetchLogSizeFromIpc();
|
|
if (fetched && Number.isFinite(fetched)) {
|
|
setLogSize(fetched);
|
|
} else {
|
|
setLogSize(0);
|
|
}
|
|
}, 1 * DURATION.SECONDS);
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const [_state, handleDeleteAllLogs] = useAsyncFn(async () => {
|
|
try {
|
|
const afterCleanSize = await ipcRenderer.invoke('delete-all-logs', true);
|
|
window.log.warn(
|
|
`[debugMenu] ClearOldLogsButton clicked. After clean: ${filesize(afterCleanSize)}`
|
|
);
|
|
setLogSize(afterCleanSize);
|
|
ToastUtils.pushToastInfo('ClearOldLogsButton', 'Cleared old logs!');
|
|
} catch (error) {
|
|
window.log.error(`[debugMenu] ClearOldLogsButton ${Errors.toString(error)}`);
|
|
ToastUtils.pushToastError('ClearOldLogsButtonError', 'Clearing logs failed! See logs');
|
|
}
|
|
});
|
|
|
|
return (
|
|
<DebugButton
|
|
onClick={() => {
|
|
void handleDeleteAllLogs();
|
|
}}
|
|
>
|
|
Clear old logs {filesize(logSize)}
|
|
</DebugButton>
|
|
);
|
|
};
|
|
|
|
function OfflineButton() {
|
|
const [isOnline, setIsOnline] = useState(window.isOnline);
|
|
|
|
return (
|
|
<DebugButton
|
|
onClick={() => {
|
|
window.isOnline = !isOnline;
|
|
setIsOnline(!isOnline);
|
|
window.log.warn(`[debugMenu] OfflineButton: Going ${isOnline ? 'offline' : 'online'}`);
|
|
}}
|
|
>
|
|
Go {isOnline ? 'offline' : 'online'}
|
|
</DebugButton>
|
|
);
|
|
}
|
|
|
|
export const LoggingDebugSection = ({ forceUpdate }: { forceUpdate: () => void }) => {
|
|
return (
|
|
<DebugMenuSection title="Logging">
|
|
<DebugButton
|
|
onClick={() => {
|
|
void saveLogToDesktop();
|
|
}}
|
|
>
|
|
<Localizer token="helpReportABugExportLogs" />
|
|
</DebugButton>
|
|
<ClearOldLogsButton />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugLogging" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugLibsessionDumps" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugBuiltSnodeRequests" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugSwarmPolling" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugServerRequests" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugNonSnodeRequests" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugOnionRequests" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugOnionPaths" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugSnodePool" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugOnlineState" />
|
|
<FlagToggle forceUpdate={forceUpdate} flag="debugInsecureNodeFetch" />
|
|
<i>"Debug Insecure Node Fetch" will log every single request/response!</i>
|
|
</DebugMenuSection>
|
|
);
|
|
};
|
|
|
|
export const Playgrounds = ({ setPage }: DebugMenuPageProps) => {
|
|
const proAvailable = getIsProAvailableMemo();
|
|
|
|
if (!proAvailable) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<DebugMenuSection title="Playgrounds" rowWrap={true}>
|
|
<DebugButton onClick={() => setPage(DEBUG_MENU_PAGE.Pro)}>Pro Playground</DebugButton>
|
|
<DebugButton onClick={() => setPage(DEBUG_MENU_PAGE.POPOVER)}>Popover Playground</DebugButton>
|
|
</DebugMenuSection>
|
|
);
|
|
};
|
|
|
|
export const DebugActions = () => {
|
|
const dispatch = getAppDispatch();
|
|
const proAvailable = getIsProAvailableMemo();
|
|
|
|
return (
|
|
<DebugMenuSection title="Actions" rowWrap={true}>
|
|
<DebugButton
|
|
buttonColor={SessionButtonColor.Danger}
|
|
onClick={() => {
|
|
dispatch(setDebugMode(false));
|
|
dispatch(updateDebugMenuModal(null));
|
|
}}
|
|
>
|
|
Exit Debug Mode
|
|
</DebugButton>
|
|
<OfflineButton />
|
|
{window.getCommitHash() ? (
|
|
<DebugButton
|
|
onClick={() => {
|
|
void shell.openExternal(
|
|
`https://github.com/session-foundation/session-desktop/commit/${window.getCommitHash()}`
|
|
);
|
|
}}
|
|
>
|
|
Go to commit
|
|
</DebugButton>
|
|
) : null}
|
|
<DebugButton
|
|
onClick={() => {
|
|
void shell.openExternal(
|
|
`https://github.com/session-foundation/session-desktop/releases/tag/v${window.getVersion()}`
|
|
);
|
|
}}
|
|
>
|
|
<Localizer token="updateReleaseNotes" />
|
|
</DebugButton>
|
|
<CheckForUpdatesButton />
|
|
<CheckVersionButton channelToCheck="stable" />
|
|
<CheckVersionButton channelToCheck="alpha" />
|
|
<DebugButton
|
|
onClick={async () => {
|
|
const storageProfile = await ipcRenderer.invoke('get-storage-profile');
|
|
void shell.openPath(storageProfile);
|
|
}}
|
|
>
|
|
Open storage profile
|
|
</DebugButton>
|
|
<DebugButton
|
|
hide={!proAvailable}
|
|
onClick={async () => {
|
|
const masterPrivKeyHex = await getProMasterKeyHex();
|
|
const rotatingPrivKeyHex = await UserUtils.getProRotatingPrivateKeyHex();
|
|
const response = await ProBackendAPI.generateProProof({
|
|
masterPrivKeyHex,
|
|
rotatingPrivKeyHex,
|
|
});
|
|
if (getFeatureFlag('debugServerRequests')) {
|
|
window?.log?.debug('getProProof response: ', response);
|
|
}
|
|
if (response?.status_code === 200) {
|
|
const proProof: ProProof = {
|
|
expiryMs: response.result.expiry_unix_ts_ms,
|
|
genIndexHashB64: response.result.gen_index_hash_b64,
|
|
rotatingPubkeyHex: response.result.rotating_pkey_hex,
|
|
version: response.result.version,
|
|
signatureHex: response.result.sig_hex,
|
|
};
|
|
await UserConfigWrapperActions.setProConfig({ proProof, rotatingPrivKeyHex });
|
|
}
|
|
}}
|
|
>
|
|
Get Pro Proof
|
|
</DebugButton>
|
|
<DebugButton
|
|
hide={!proAvailable}
|
|
onClick={async () => {
|
|
const masterPrivKeyHex = await getProMasterKeyHex();
|
|
const response = await ProBackendAPI.getProDetails({ masterPrivKeyHex });
|
|
if (getFeatureFlag('debugServerRequests')) {
|
|
window?.log?.debug('Pro Details: ', response);
|
|
}
|
|
}}
|
|
>
|
|
Get Pro Details
|
|
</DebugButton>
|
|
<DebugButton
|
|
hide={!proAvailable}
|
|
onClick={async () => {
|
|
const response = await ProBackendAPI.getRevocationList({ ticket: 0 });
|
|
if (getFeatureFlag('debugServerRequests')) {
|
|
window?.log?.debug('Pro Revocation List: ', response);
|
|
}
|
|
}}
|
|
>
|
|
Get Pro Revocation List (from ticket 0)
|
|
</DebugButton>
|
|
</DebugMenuSection>
|
|
);
|
|
};
|
|
|
|
export const DebugUrlInteractionsSection = () => {
|
|
const [urlInteractions, setUrlInteractions] = useState(getUrlInteractions());
|
|
|
|
const refresh = useCallback(() => setUrlInteractions(getUrlInteractions()), []);
|
|
const removeUrl = useCallback(
|
|
async (url: string) => {
|
|
await removeUrlInteractionHistory(url);
|
|
refresh();
|
|
},
|
|
[refresh]
|
|
);
|
|
const clearAll = useCallback(async () => {
|
|
await clearAllUrlInteractions();
|
|
refresh();
|
|
}, [refresh]);
|
|
|
|
return (
|
|
<DebugMenuSection title="Url Interactions">
|
|
<DebugButton onClick={clearAll} buttonColor={SessionButtonColor.Danger}>
|
|
Clear All
|
|
</DebugButton>
|
|
<DebugButton onClick={refresh}>Refresh</DebugButton>
|
|
<table>
|
|
<tr>
|
|
<th>URL</th>
|
|
<th>Interactions</th>
|
|
<th>Last Updated</th>
|
|
</tr>
|
|
{urlInteractions.map(({ url, interactions, lastUpdated }) => {
|
|
const updatedStr = formatRoundedUpTimeUntilTimestamp(lastUpdated);
|
|
return (
|
|
<tr key={url}>
|
|
<td>{url}</td> <td>{interactions.map(urlInteractionToString).join(', ')}</td>{' '}
|
|
<td>{updatedStr}</td>{' '}
|
|
<td>
|
|
<DebugButton
|
|
buttonColor={SessionButtonColor.Danger}
|
|
onClick={async () => removeUrl(url)}
|
|
>
|
|
<LucideIcon unicode={LUCIDE_ICONS_UNICODE.TRASH2} iconSize="small" />
|
|
</DebugButton>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</table>
|
|
</DebugMenuSection>
|
|
);
|
|
};
|
|
|
|
export const DebugCtaInteractionsSection = () => {
|
|
const [ctaInteractions, setCtaInteractions] = useState(getCtaInteractions());
|
|
|
|
const refresh = useCallback(() => setCtaInteractions(getCtaInteractions()), []);
|
|
const removeCta = useCallback(
|
|
async (variant: CTAVariant) => {
|
|
await removeCtaInteractionHistory(variant);
|
|
refresh();
|
|
},
|
|
[refresh]
|
|
);
|
|
const clearAll = useCallback(async () => {
|
|
await clearAllCtaInteractions();
|
|
refresh();
|
|
}, [refresh]);
|
|
|
|
return (
|
|
<DebugMenuSection title="CTA Interactions">
|
|
<DebugButton onClick={clearAll} buttonColor={SessionButtonColor.Danger}>
|
|
Clear All
|
|
</DebugButton>
|
|
<DebugButton onClick={refresh}>Refresh</DebugButton>
|
|
<table>
|
|
<tr>
|
|
<th>CTA</th>
|
|
<th>Open</th>
|
|
<th>Close</th>
|
|
<th>Action</th>
|
|
<th>Cancel</th>
|
|
<th>Last Updated</th>
|
|
</tr>
|
|
{ctaInteractions.map(({ variant, lastUpdated, open, action, cancel, close }) => {
|
|
const updatedStr = formatRoundedUpTimeUntilTimestamp(lastUpdated);
|
|
return (
|
|
<tr key={variant}>
|
|
<td>{variant}</td> <td>{open ?? 0}</td> <td>{close ?? 0}</td> <td>{action ?? 0}</td>{' '}
|
|
<td>{cancel ?? 0}</td> <td>{updatedStr}</td>{' '}
|
|
<td>
|
|
<DebugButton
|
|
buttonColor={SessionButtonColor.Danger}
|
|
onClick={async () => removeCta(variant)}
|
|
>
|
|
<LucideIcon unicode={LUCIDE_ICONS_UNICODE.TRASH2} iconSize="small" />
|
|
</DebugButton>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</table>
|
|
</DebugMenuSection>
|
|
);
|
|
};
|
|
|
|
export const ExperimentalActions = ({ forceUpdate }: { forceUpdate: () => void }) => {
|
|
const dispatch = getAppDispatch();
|
|
// const refreshedAt = useReleasedFeaturesRefreshedAt();
|
|
// const sesh101NotificationAt = useSesh101NotificationAt();
|
|
|
|
// const [countdown, setCountdown] = useState(false);
|
|
// const timeLeftMs = sesh101NotificationAt - Date.now();
|
|
|
|
// TODO [SES-2606] uncomment before release but after QA
|
|
// if (!isDebugMode()) {
|
|
// return null;
|
|
// }
|
|
|
|
return (
|
|
<DebugMenuSection title="Experimental Actions 🚨" rowWrap={true}>
|
|
<DebugButton
|
|
onClick={() => {
|
|
dispatch(releasedFeaturesActions.resetExperiments() as any);
|
|
forceUpdate();
|
|
}}
|
|
>
|
|
Reset experiments
|
|
</DebugButton>
|
|
|
|
{/* <SessionButton
|
|
onClick={() => {
|
|
dispatch(releasedFeaturesActions.updateSesh101NotificationAt(notifyAt));
|
|
setCountdown(true);
|
|
}}
|
|
>
|
|
Notify Sesh 101
|
|
<span style={{ marginInlineStart: 'var(--margins-xs)' }}>
|
|
{countdown
|
|
? Math.floor(timeLeftMs / 1000) > 0
|
|
? `(${formatAbbreviatedExpireDoubleTimer(Math.floor(timeLeftMs / 1000))})`
|
|
: '🎉'
|
|
: '(10s)'}
|
|
</span>
|
|
</SessionButton> */}
|
|
<DebugButton
|
|
onClick={() => {
|
|
dispatch(networkDataActions.fetchInfoFromSeshServer() as any);
|
|
}}
|
|
>
|
|
Network Info Request (Force)
|
|
</DebugButton>
|
|
</DebugMenuSection>
|
|
);
|
|
};
|
|
|
|
async function fetchContactsCountAndUpdate() {
|
|
const count = (await ContactsWrapperActions.getAll()).length;
|
|
if (count && Number.isFinite(count)) {
|
|
return count;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const StyledDummyContactsContainer = styled.div``;
|
|
|
|
function AddDummyContactButton() {
|
|
const [loading, setLoading] = useState(false);
|
|
const [addedCount, setAddedCount] = useState(0);
|
|
const [countToAdd, setCountToAdd] = useState(500);
|
|
|
|
const { data: contactsCount } = usePolling(
|
|
fetchContactsCountAndUpdate,
|
|
500,
|
|
'AddDummyContactButton'
|
|
);
|
|
|
|
async function doIt() {
|
|
if (loading) {
|
|
return;
|
|
}
|
|
try {
|
|
setLoading(true);
|
|
setAddedCount(0);
|
|
const chunkSize = 10;
|
|
const allIndexes = Array.from({ length: countToAdd }).map((_unused, i) => i);
|
|
const chunks = chunk(allIndexes, chunkSize);
|
|
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await Promise.all(chunks[chunkIndex].map(() => generateOneRandomContact()));
|
|
setAddedCount(Math.min(chunkIndex * chunkSize, countToAdd));
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
setAddedCount(0);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<StyledDummyContactsContainer>
|
|
<SimpleSessionInput
|
|
autoFocus={false}
|
|
type="text"
|
|
value={`${countToAdd}`}
|
|
onValueChanged={(value: string) => {
|
|
const asNumber = toNumber(value);
|
|
if (Number.isFinite(asNumber)) {
|
|
setCountToAdd(asNumber);
|
|
}
|
|
}}
|
|
disabled={loading}
|
|
maxLength={10}
|
|
errorDataTestId="invalid-data-testid"
|
|
onEnterPressed={() => void doIt()}
|
|
providedError={undefined}
|
|
/>
|
|
<DebugButton onClick={doIt} disabled={loading}>
|
|
{loading ? (
|
|
<>
|
|
{addedCount}/{countToAdd}...
|
|
</>
|
|
) : (
|
|
`Add ${countToAdd} contacts (current: ${contactsCount})`
|
|
)}
|
|
</DebugButton>
|
|
</StyledDummyContactsContainer>
|
|
);
|
|
}
|
|
|
|
export const DataGenerationActions = () => {
|
|
return (
|
|
<DebugMenuSection title="Data generation">
|
|
<AddDummyContactButton />
|
|
</DebugMenuSection>
|
|
);
|
|
};
|
|
|
|
export const AboutInfo = () => {
|
|
const environmentStates = [];
|
|
|
|
if (window.getEnvironment() !== 'production') {
|
|
environmentStates.push(window.getEnvironment());
|
|
}
|
|
|
|
if (window.getAppInstance()) {
|
|
environmentStates.push(window.getAppInstance());
|
|
}
|
|
|
|
const aboutInfo = [
|
|
`${tr('updateVersion', { version: window.getVersion() })}`,
|
|
`${tr('systemInformationDesktop', { information: window.getOSRelease() })}`,
|
|
`${tr('commitHashDesktop', { hash: window.getCommitHash() || tr('unknown') })}`,
|
|
`Libsession Hash: ${LIBSESSION_CONSTANTS.LIBSESSION_UTIL_VERSION || tr('unknown')}`,
|
|
`Libsession NodeJS Version: ${LIBSESSION_CONSTANTS.LIBSESSION_NODEJS_VERSION || tr('unknown')}`,
|
|
`Libsession NodeJS Hash: ${LIBSESSION_CONSTANTS.LIBSESSION_NODEJS_COMMIT || tr('unknown')}`,
|
|
`User Agent:${window.navigator.userAgent ? `\n\t${window.navigator.userAgent.split(') ').join(') \n\t')}` : tr('unknown')}`,
|
|
`${environmentStates.join(' - ')}`,
|
|
];
|
|
|
|
return (
|
|
<Flex
|
|
$container={true}
|
|
width={'100%'}
|
|
$flexDirection="column"
|
|
$justifyContent="flex-start"
|
|
$alignItems="flex-start"
|
|
$flexWrap="wrap"
|
|
>
|
|
<SpacerXS />
|
|
<Flex $container={true} width="100%" $alignItems="center" $flexGap="var(--margins-xs)">
|
|
<h2>About</h2>
|
|
<CopyToClipboardIcon
|
|
iconSize={'small'}
|
|
copyContent={aboutInfo.join('\n')}
|
|
buttonColor={SessionButtonColor.None}
|
|
/>
|
|
</Flex>
|
|
<Flex
|
|
$container={true}
|
|
width="100%"
|
|
$flexDirection="column"
|
|
$justifyContent="space-between"
|
|
$alignItems="center"
|
|
$flexGap="var(--margins-xs)"
|
|
>
|
|
{aboutInfo.map((info, index) => {
|
|
if (!info) {
|
|
return null;
|
|
}
|
|
return (
|
|
<Flex
|
|
key={`debug-about-info-${index}`}
|
|
$container={true}
|
|
width="100%"
|
|
$alignItems="flex-start"
|
|
$flexGap="var(--margins-xs)"
|
|
>
|
|
<p style={{ userSelect: 'text', lineHeight: 1.5 }}>{info}</p>
|
|
<CopyToClipboardIcon
|
|
iconSize={'small'}
|
|
copyContent={info}
|
|
buttonColor={SessionButtonColor.None}
|
|
/>
|
|
</Flex>
|
|
);
|
|
})}
|
|
<SpacerXS />
|
|
</Flex>
|
|
</Flex>
|
|
);
|
|
};
|
|
|
|
export const OtherInfo = () => {
|
|
const otherInfo = useAsync(async () => {
|
|
const { id, vbid } = await window.getUserKeys();
|
|
const proMasterKey = await getProMasterKeyHex();
|
|
const result = [
|
|
`${tr('accountIdYours')}: ${id}`,
|
|
`VBID: ${vbid}`,
|
|
`Pro Public Master Key: ${proMasterKey?.slice(64)}`,
|
|
`Pro Private Master Key: ${proMasterKey?.slice(0, 64)}`,
|
|
];
|
|
return result;
|
|
}, []);
|
|
|
|
return (
|
|
<Flex
|
|
$container={true}
|
|
width={'100%'}
|
|
$flexDirection="column"
|
|
$justifyContent="flex-start"
|
|
$alignItems="flex-start"
|
|
$flexWrap="wrap"
|
|
>
|
|
<SpacerXS />
|
|
<Flex $container={true} width="100%" $alignItems="center" $flexGap="var(--margins-xs)">
|
|
<h2>Other Info</h2>
|
|
{otherInfo.value ? (
|
|
<CopyToClipboardIcon
|
|
iconSize={'small'}
|
|
copyContent={otherInfo.value.join('\n')}
|
|
buttonColor={SessionButtonColor.None}
|
|
/>
|
|
) : null}
|
|
</Flex>
|
|
<Flex
|
|
$container={true}
|
|
width="100%"
|
|
$flexDirection="column"
|
|
$justifyContent="space-between"
|
|
$alignItems="center"
|
|
$flexGap="var(--margins-xs)"
|
|
>
|
|
{otherInfo.loading ? (
|
|
<p>{tr('loading')}</p>
|
|
) : otherInfo.error ? (
|
|
<p style={{ color: 'var(--danger-color)', userSelect: 'text' }}>
|
|
{tr('theError')}: {otherInfo.error.message || tr('errorUnknown')}
|
|
</p>
|
|
) : null}
|
|
{otherInfo.value
|
|
? otherInfo.value.map((info, index) => (
|
|
<Flex
|
|
key={`debug-other-info-${index}`}
|
|
$container={true}
|
|
width="100%"
|
|
$alignItems="flex-start"
|
|
$flexGap="var(--margins-xs)"
|
|
>
|
|
<p
|
|
style={{
|
|
userSelect: 'text',
|
|
lineHeight: 1.5,
|
|
fontSize: 'var(--font-size-small)',
|
|
}}
|
|
>
|
|
{info}
|
|
</p>
|
|
<CopyToClipboardIcon
|
|
iconSize={'small'}
|
|
copyContent={info}
|
|
buttonColor={SessionButtonColor.None}
|
|
/>
|
|
</Flex>
|
|
))
|
|
: null}
|
|
</Flex>
|
|
</Flex>
|
|
);
|
|
};
|