diff --git a/ts/components/dialog/ModalContainer.tsx b/ts/components/dialog/ModalContainer.tsx
index 0eecfde9e..8341171e0 100644
--- a/ts/components/dialog/ModalContainer.tsx
+++ b/ts/components/dialog/ModalContainer.tsx
@@ -5,6 +5,7 @@ import {
getBlockOrUnblockUserModalState,
getChangeNickNameDialog,
getConfirmModal,
+ getDebugMenuModalState,
getDeleteAccountModalState,
getEditProfileDialog,
getEditProfilePictureModalState,
@@ -43,6 +44,7 @@ import { UserDetailsDialog } from './UserDetailsDialog';
import { EditProfileDialog } from './edit-profile/EditProfileDialog';
import { OpenUrlModal } from './OpenUrlModal';
import { BlockOrUnblockDialog } from './blockOrUnblock/BlockOrUnblockDialog';
+import { DebugMenuModal } from './debug/DebugMenuModal';
export const ModalContainer = () => {
const confirmModalState = useSelector(getConfirmModal);
@@ -66,9 +68,18 @@ export const ModalContainer = () => {
const hideRecoveryPasswordModalState = useSelector(getHideRecoveryPasswordModalState);
const openUrlModalState = useSelector(getOpenUrlModalState);
const lightBoxOptions = useSelector(getLightBoxOptions);
+ const debugMenuModalState = useSelector(getDebugMenuModalState);
+ // NOTE the order of the modals is important for the z-index
return (
<>
+ {/* Screens */}
+ {sessionPasswordModalState && }
+ {editProfileModalState && }
+ {onionPathModalState && }
+ {reactListModalState && }
+ {debugMenuModalState && }
+ {/* Actions */}
{banOrUnbanUserModalState && }
{blockOrUnblockModalState && }
{inviteModalState && }
@@ -80,13 +91,8 @@ export const ModalContainer = () => {
{updateGroupNameModalState && }
{userDetailsModalState && }
{changeNicknameModal && }
- {editProfileModalState && }
- {onionPathModalState && }
{enterPasswordModalState && }
- {sessionPasswordModalState && }
{deleteAccountModalState && }
- {confirmModalState && }
- {reactListModalState && }
{reactClearAllModalState && }
{editProfilePictureModalState && (
@@ -94,8 +100,10 @@ export const ModalContainer = () => {
{hideRecoveryPasswordModalState && (
)}
- {openUrlModalState && }
{lightBoxOptions && }
+ {openUrlModalState && }
+ {/* Should be on top of all other modals */}
+ {confirmModalState && }
>
);
};
diff --git a/ts/components/dialog/debug/DebugMenuModal.tsx b/ts/components/dialog/debug/DebugMenuModal.tsx
new file mode 100644
index 000000000..b0b5d4d34
--- /dev/null
+++ b/ts/components/dialog/debug/DebugMenuModal.tsx
@@ -0,0 +1,60 @@
+import { AnimatePresence } from 'framer-motion';
+import styled from 'styled-components';
+import { useDispatch } from 'react-redux';
+import { Flex } from '../../basic/Flex';
+import { SpacerMD, SpacerSM } from '../../basic/Text';
+import { updateDebugMenuModal } from '../../../state/ducks/modalDialog';
+import { AboutInfo, DebugActions, FeatureFlags, OtherInfo } from './components';
+import { SessionWrapperModal } from '../../SessionWrapperModal';
+
+const StyledContent = styled(Flex)`
+ padding-inline: var(--margins-sm);
+
+ h2 {
+ font-size: var(--font-size-xl);
+ }
+
+ h2,
+ h3 {
+ margin: var(--margins-md) 0;
+ padding: 0;
+ text-decoration: underline;
+ }
+
+ p,
+ i {
+ line-height: 1.4;
+ margin: 0;
+ padding: 0;
+ text-align: start;
+ }
+`;
+
+export function DebugMenuModal() {
+ const dispatch = useDispatch();
+
+ const onClose = () => {
+ dispatch(updateDebugMenuModal(null));
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/ts/components/dialog/debug/components.tsx b/ts/components/dialog/debug/components.tsx
new file mode 100644
index 000000000..d7632fe88
--- /dev/null
+++ b/ts/components/dialog/debug/components.tsx
@@ -0,0 +1,310 @@
+import { isBoolean } from 'lodash';
+import useUpdate from 'react-use/lib/useUpdate';
+import useAsync from 'react-use/lib/useAsync';
+import { shell } from 'electron';
+import useBoolean from 'react-use/lib/useBoolean';
+import type { SessionFeatureFlagsKeys } from '../../../window';
+import { Flex } from '../../basic/Flex';
+import { SessionToggle } from '../../basic/SessionToggle';
+import { HintText, SpacerXS } from '../../basic/Text';
+import { localize } from '../../../localization/localeTools';
+import { CopyToClipboardIcon } from '../../buttons';
+import { saveLogToDesktop } from '../../../util/logging';
+import { Localizer } from '../../basic/Localizer';
+import { SessionButton } from '../../basic/SessionButton';
+import { ToastUtils, UserUtils } from '../../../session/utils';
+import { getLatestReleaseFromFileServer } from '../../../session/apis/file_server_api/FileServerApi';
+import { SessionSpinner } from '../../loading';
+
+export const DebugActions = () => {
+ const [loadingLatestRelease, setLoadingLatestRelease] = useBoolean(false);
+
+ return (
+ <>
+
Actions
+
+
+ {
+ void saveLogToDesktop();
+ }}
+ >
+
+
+
+ {window.getCommitHash() ? (
+ {
+ void shell.openExternal(
+ `https://github.com/session-foundation/session-desktop/commit/${window.getCommitHash()}`
+ );
+ }}
+ >
+ Go to commit
+
+ ) : null}
+
+ {
+ void shell.openExternal(
+ `https://github.com/session-foundation/session-desktop/releases/tag/v${window.getVersion()}`
+ );
+ }}
+ >
+
+
+
+ {
+ const userEd25519SecretKey = (await UserUtils.getUserED25519KeyPairBytes())
+ ?.privKeyBytes;
+ if (!userEd25519SecretKey) {
+ window.log.error('[debugMenu] no userEd25519SecretKey');
+ return;
+ }
+ setLoadingLatestRelease(true);
+ const versionNumber = await getLatestReleaseFromFileServer(userEd25519SecretKey);
+ setLoadingLatestRelease(false);
+
+ if (versionNumber) {
+ ToastUtils.pushToastInfo('debugLatestRelease', `v${versionNumber}`);
+ } else {
+ ToastUtils.pushToastError('debugLatestRelease', 'Failed to fetch latest release');
+ }
+ }}
+ >
+
+ {!loadingLatestRelease ? 'Check latest release' : null}
+
+
+ >
+ );
+};
+
+const unsupportedFlags = ['useTestNet'];
+const untestedFlags = ['useOnionRequests', 'useClosedGroupV3', 'replaceLocalizedStringsWithKeys'];
+
+const handleFeatureFlagToggle = async (
+ forceUpdate: () => void,
+ flag: SessionFeatureFlagsKeys,
+ parentFlag?: SessionFeatureFlagsKeys
+) => {
+ const currentValue = parentFlag
+ ? (window as any).sessionFeatureFlags[parentFlag][flag]
+ : (window as any).sessionFeatureFlags[flag];
+
+ if (parentFlag) {
+ (window as any).sessionFeatureFlags[parentFlag][flag] = !currentValue;
+ window.log.debug(`[debugMenu] toggled ${parentFlag}.${flag} to ${!currentValue}`);
+ } else {
+ (window as any).sessionFeatureFlags[flag] = !currentValue;
+ window.log.debug(`[debugMenu] toggled ${flag} to ${!currentValue}`);
+ }
+
+ forceUpdate();
+};
+
+const FlagToggle = ({
+ forceUpdate,
+ flag,
+ value,
+ parentFlag,
+}: {
+ forceUpdate: () => void;
+ flag: SessionFeatureFlagsKeys;
+ value: any;
+ parentFlag?: SessionFeatureFlagsKeys;
+}) => {
+ const key = `feature-flag-toggle${parentFlag ? `-${parentFlag}` : ''}-${flag}`;
+ return (
+
+
+ {flag}
+ {untestedFlags.includes(flag) ? Untested : null}
+
+ void handleFeatureFlagToggle(forceUpdate, flag, parentFlag)}
+ />
+
+ );
+};
+
+export const FeatureFlags = ({ flags }: { flags: Record }) => {
+ const forceUpdate = useUpdate();
+ return (
+
+
+ Feature Flags
+ Experimental
+
+
+ Changes are temporary. You can clear them by reloading the window or restarting the app.
+
+
+ {Object.entries(flags).map(([key, value]) => {
+ const flag = key as SessionFeatureFlagsKeys;
+ if (unsupportedFlags.includes(flag)) {
+ return null;
+ }
+
+ if (!isBoolean(value)) {
+ return (
+ <>
+ {flag}
+ {Object.entries(value).map(([k, v]: [string, any]) => {
+ const nestedFlag = k as SessionFeatureFlagsKeys;
+ return (
+
+ );
+ })}
+ >
+ );
+ }
+ return ;
+ })}
+
+ );
+};
+
+export const AboutInfo = () => {
+ const environmentStates = [];
+
+ if (window.getEnvironment() !== 'production') {
+ environmentStates.push(window.getEnvironment());
+ }
+
+ if (window.getAppInstance()) {
+ environmentStates.push(window.getAppInstance());
+ }
+
+ const aboutInfo = [
+ `${localize('updateVersion').withArgs({ version: window.getVersion() })}`,
+ `${localize('systemInformationDesktop').withArgs({ information: window.getOSRelease() })}`,
+ `${localize('commitHashDesktop').withArgs({ hash: window.getCommitHash() || window.i18n('unknown') })}`,
+ `${environmentStates.join(' - ')}`,
+ ];
+
+ return (
+
+
+
+ About
+
+
+
+ {aboutInfo.map((info, index) => (
+
+ {info}
+
+
+ ))}
+
+
+
+ );
+};
+
+export const OtherInfo = () => {
+ const otherInfo = useAsync(async () => {
+ const { id, vbid } = await window.getUserKeys();
+ return [`${localize('accountIdYours')}: ${id}`, `VBID: ${vbid}`];
+ }, []);
+
+ return (
+
+
+
+ Other Info
+ {otherInfo.value ? (
+
+ ) : null}
+
+
+ {otherInfo.loading ? (
+ {localize('loading')}
+ ) : otherInfo.error ? (
+
+
+ {localize('theError')}: {otherInfo.error.message || localize('errorUnknown')}
+
+
+ ) : null}
+ {otherInfo.value
+ ? otherInfo.value.map((info, index) => (
+
+ {info}
+
+
+ ))
+ : null}
+
+
+ );
+};
diff --git a/ts/state/ducks/modalDialog.tsx b/ts/state/ducks/modalDialog.tsx
index d19fc027e..787a78e33 100644
--- a/ts/state/ducks/modalDialog.tsx
+++ b/ts/state/ducks/modalDialog.tsx
@@ -54,6 +54,8 @@ export type LightBoxOptions = {
onClose?: () => void;
} | null;
+export type DebugMenuModalState = object | null;
+
export type ModalState = {
confirmModal: ConfirmModalState;
inviteContactModal: InviteContactModalState;
@@ -76,6 +78,7 @@ export type ModalState = {
hideRecoveryPasswordModalState: HideRecoveryPasswordModalState;
openUrlModal: OpenUrlModalState;
lightBoxOptions: LightBoxOptions;
+ debugMenuModal: DebugMenuModalState;
};
export const initialModalState: ModalState = {
@@ -100,6 +103,7 @@ export const initialModalState: ModalState = {
hideRecoveryPasswordModalState: null,
openUrlModal: null,
lightBoxOptions: null,
+ debugMenuModal: null,
};
const ModalSlice = createSlice({
@@ -183,6 +187,9 @@ const ModalSlice = createSlice({
return { ...state, lightBoxOptions };
},
+ updateDebugMenuModal(state, action: PayloadAction) {
+ return { ...state, debugMenuModal: action.payload };
+ },
},
});
@@ -209,5 +216,6 @@ export const {
updateHideRecoveryPasswordModal,
updateOpenUrlModal,
updateLightBoxOptions,
+ updateDebugMenuModal,
} = actions;
export const modalReducer = reducer;
diff --git a/ts/state/selectors/modal.ts b/ts/state/selectors/modal.ts
index bfb0913b3..2891b4038 100644
--- a/ts/state/selectors/modal.ts
+++ b/ts/state/selectors/modal.ts
@@ -142,3 +142,5 @@ export const getLightBoxOptions = createSelector(
getModal,
(state: ModalState): LightBoxOptions => state.lightBoxOptions
);
+
+export const getDebugMenuModalState = (state: StateType) => getModal(state).debugMenuModal;