diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index af3f62d7d..064a5460a 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -137,6 +137,13 @@
"typingIndicatorsSettingDescription": "See and share when messages are being typed (applies to all sessions).",
"typingIndicatorsSettingTitle": "Typing Indicators",
"zoomFactorSettingTitle": "Zoom Factor",
+ "pruneSettingTitle": "Prune Old Open Group Messages",
+ "pruneSettingDescription": "Prune messages older than 6 months from Open Groups on start",
+ "pruningOpengroupDialogTitle": "Open group pruning",
+ "pruningOpengroupDialogMessage": "Pruning old open group messages improves performance. Enable pruning for open group messages older than 6 months?",
+ "pruningOpengroupDialogSubMessage": "You can change this setting in the Session settings menu",
+ "enable": "Enable",
+ "keepDisabled": "Keep disabled",
"notificationSettingsDialog": "When messages arrive, display notifications that reveal...",
"disableNotifications": "Mute notifications",
"nameAndMessage": "Name and content",
diff --git a/preload.js b/preload.js
index 7fafda6e7..abae0e161 100644
--- a/preload.js
+++ b/preload.js
@@ -86,6 +86,28 @@ window.getStartInTray = async () => {
});
};
+window.getOpengroupPruning = async () => {
+ return new Promise(resolve => {
+ ipc.once('get-opengroup-pruning-response', (_event, value) => {
+ resolve(value);
+ });
+ ipc.send('get-opengroup-pruning');
+ });
+};
+
+window.setOpengroupPruning = async opengroupPruning =>
+ new Promise((resolve, reject) => {
+ ipc.once('set-opengroup-pruning-response', (_event, error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ return;
+ });
+ ipc.send('set-opengroup-pruning', opengroupPruning);
+ });
+
window._ = require('lodash');
// We never do these in our code, so we'll prevent it everywhere
diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss
index 53f23bd74..2ace0f7db 100644
--- a/stylesheets/_session.scss
+++ b/stylesheets/_session.scss
@@ -551,7 +551,9 @@ label {
&-main-message {
font-size: $session-font-md;
+ line-height: 1.5;
}
+
&-sub-message {
text-align: center;
margin-top: 20px;
diff --git a/ts/components/dialog/SessionConfirm.tsx b/ts/components/dialog/SessionConfirm.tsx
index b0b9534d0..805d1e042 100644
--- a/ts/components/dialog/SessionConfirm.tsx
+++ b/ts/components/dialog/SessionConfirm.tsx
@@ -127,6 +127,12 @@ export const SessionConfirm = (props: SessionConfirmDialogProps) => {
+
{!hideCancel && (
{
dataTestId="session-confirm-cancel-button"
/>
)}
-
);
diff --git a/ts/components/leftpane/ActionsPanel.tsx b/ts/components/leftpane/ActionsPanel.tsx
index da6b67108..dceb78031 100644
--- a/ts/components/leftpane/ActionsPanel.tsx
+++ b/ts/components/leftpane/ActionsPanel.tsx
@@ -30,7 +30,11 @@ import { cleanUpOldDecryptedMedias } from '../../session/crypto/DecryptedAttachm
import { DURATION } from '../../session/constants';
import { conversationChanged, conversationRemoved } from '../../state/ducks/conversations';
-import { editProfileModal, onionPathModal } from '../../state/ducks/modalDialog';
+import {
+ editProfileModal,
+ onionPathModal,
+ updateConfirmModal,
+} from '../../state/ducks/modalDialog';
import { uploadOurAvatar } from '../../interactions/conversationInteractions';
import { ModalContainer } from '../dialog/ModalContainer';
import { debounce, isEmpty, isString } from 'lodash';
@@ -54,6 +58,9 @@ import { getLatestDesktopReleaseFileToFsV2 } from '../../session/apis/file_serve
import { ipcRenderer } from 'electron';
import { UserUtils } from '../../session/utils';
+import { Storage } from '../../util/storage';
+import { SettingsKey } from '../../data/settings-key';
+
const Section = (props: { type: SectionType }) => {
const ourNumber = useSelector(getOurNumber);
const unreadMessageCount = useSelector(getUnreadMessageCount);
@@ -251,6 +258,8 @@ const doAppStartUp = () => {
void loadDefaultRooms();
debounce(triggerAvatarReUploadIfNeeded, 200);
+
+ void askEnablingOpengroupPruningIfNeeded();
};
const CallContainer = () => {
@@ -279,6 +288,36 @@ async function fetchReleaseFromFSAndUpdateMain() {
}
}
+async function askEnablingOpengroupPruningIfNeeded() {
+ if (Storage.get(SettingsKey.settingsOpengroupPruning) === undefined) {
+ const setSettingsAndCloseDialog = async (valueToSetPruningTo: boolean) => {
+ window.setSettingValue(SettingsKey.settingsOpengroupPruning, valueToSetPruningTo);
+ await window.setOpengroupPruning(valueToSetPruningTo);
+ window.inboxStore?.dispatch(updateConfirmModal(null));
+ };
+ window.inboxStore?.dispatch(
+ updateConfirmModal({
+ onClickOk: async () => {
+ await setSettingsAndCloseDialog(true);
+ },
+ onClickClose: async () => {
+ await setSettingsAndCloseDialog(false);
+ },
+ onClickCancel: async () => {
+ await setSettingsAndCloseDialog(false);
+ },
+ title: window.i18n('pruningOpengroupDialogTitle'),
+ message: window.i18n('pruningOpengroupDialogMessage'),
+ messageSub: window.i18n('pruningOpengroupDialogSubMessage'),
+ okText: window.i18n('enable'),
+ cancelText: window.i18n('keepDisabled'),
+ })
+ );
+ return;
+ }
+ // otherwise nothing to do. the settings is already on or off, but as expected by the user
+}
+
/**
* ActionsPanel is the far left banner (not the left pane).
* The panel with buttons to switch between the message/contact/settings/theme views
diff --git a/ts/components/settings/section/CategoryAppearance.tsx b/ts/components/settings/section/CategoryAppearance.tsx
index 39e1b237b..31a3fffd0 100644
--- a/ts/components/settings/section/CategoryAppearance.tsx
+++ b/ts/components/settings/section/CategoryAppearance.tsx
@@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports
import useUpdate from 'react-use/lib/useUpdate';
import { createOrUpdateItem, hasLinkPreviewPopupBeenDisplayed } from '../../../data/data';
+import { SettingsKey } from '../../../data/settings-key';
import { ToastUtils } from '../../../session/utils';
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { toggleAudioAutoplay } from '../../../state/ducks/userConfig';
@@ -15,8 +16,8 @@ import { SessionSettingButtonItem, SessionToggleWithDescription } from '../Sessi
import { ZoomingSessionSlider } from '../ZoomingSessionSlider';
async function toggleLinkPreviews() {
- const newValue = !window.getSettingValue('link-preview-setting');
- window.setSettingValue('link-preview-setting', newValue);
+ const newValue = !window.getSettingValue(SettingsKey.settingsLinkPreview);
+ window.setSettingValue(SettingsKey.settingsLinkPreview, newValue);
if (!newValue) {
await createOrUpdateItem({ id: hasLinkPreviewPopupBeenDisplayed, value: false });
} else {
@@ -35,7 +36,7 @@ async function toggleStartInTray() {
const newValue = !(await window.getStartInTray());
// make sure to write it here too, as this is the value used on the UI to mark the toggle as true/false
- window.setSettingValue('start-in-tray-setting', newValue);
+ window.setSettingValue(SettingsKey.settingsStartInTray, newValue);
await window.setStartInTray(newValue);
if (!newValue) {
ToastUtils.pushRestartNeeded();
@@ -45,11 +46,6 @@ async function toggleStartInTray() {
}
}
-const settingsMenuBar = 'hide-menu-bar';
-const settingsSpellCheck = 'spell-check';
-const settingsLinkPreview = 'link-preview-setting';
-const settingsStartInTray = 'start-in-tray-setting';
-
export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null }) => {
const dispatch = useDispatch();
const forceUpdate = useUpdate();
@@ -57,17 +53,17 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null
if (props.hasPassword !== null) {
const isHideMenuBarActive =
- window.getSettingValue(settingsMenuBar) === undefined
+ window.getSettingValue(SettingsKey.settingsMenuBar) === undefined
? true
- : window.getSettingValue(settingsMenuBar);
+ : window.getSettingValue(SettingsKey.settingsMenuBar);
const isSpellCheckActive =
- window.getSettingValue(settingsSpellCheck) === undefined
+ window.getSettingValue(SettingsKey.settingsSpellCheck) === undefined
? true
- : window.getSettingValue(settingsSpellCheck);
+ : window.getSettingValue(SettingsKey.settingsSpellCheck);
- const isLinkPreviewsOn = Boolean(window.getSettingValue(settingsLinkPreview));
- const isStartInTrayActive = Boolean(window.getSettingValue(settingsStartInTray));
+ const isLinkPreviewsOn = Boolean(window.getSettingValue(SettingsKey.settingsLinkPreview));
+ const isStartInTrayActive = Boolean(window.getSettingValue(SettingsKey.settingsStartInTray));
return (
<>
@@ -119,6 +115,7 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null
description={window.i18n('audioMessageAutoplayDescription')}
active={audioAutoPlay}
/>
+
void;
}) => {
const forceUpdate = useUpdate();
const dispatch = useDispatch();
-
+ const isOpengroupPruningEnabled = Boolean(
+ window.getSettingValue(SettingsKey.settingsOpengroupPruning)
+ );
if (props.hasPassword !== null) {
return (
<>
@@ -117,6 +132,15 @@ export const SettingsCategoryPrivacy = (props: {
description={window.i18n('hideRequestBannerDescription')}
active={useSelector(getHideMessageRequestBanner)}
/>
+ {
+ await toggleOpengroupPruning();
+ forceUpdate();
+ }}
+ title={window.i18n('pruneSettingTitle')}
+ description={window.i18n('pruneSettingDescription')}
+ active={isOpengroupPruningEnabled}
+ />
{!props.hasPassword && (
{
sendResponse(localisedError || 'Invalid password');
}
});
+
ipc.on('start-in-tray-on-start', (event, newValue) => {
try {
userConfig.set('startInTray', newValue);
@@ -954,6 +957,24 @@ ipc.on('get-start-in-tray', event => {
}
});
+ipc.on('get-opengroup-pruning', event => {
+ try {
+ const val = userConfig.get('opengroupPruning');
+ event.sender.send('get-opengroup-pruning-response', val);
+ } catch (e) {
+ event.sender.send('get-opengroup-pruning-response', false);
+ }
+});
+
+ipc.on('set-opengroup-pruning', (event, newValue) => {
+ try {
+ userConfig.set('opengroupPruning', newValue);
+ event.sender.send('set-opengroup-pruning-response', null);
+ } catch (e) {
+ event.sender.send('set-opengroup-pruning-response', e);
+ }
+});
+
ipc.on('set-password', async (event, passPhrase, oldPhrase) => {
const sendResponse = (response: string | undefined) => {
event.sender.send('set-password-response', response);
diff --git a/ts/node/sql.ts b/ts/node/sql.ts
index 7ca85ba7e..0a0edb60c 100644
--- a/ts/node/sql.ts
+++ b/ts/node/sql.ts
@@ -1551,6 +1551,7 @@ async function initializeSql({
console.info('total message count before cleaning: ', getMessageCount());
console.info('total conversation count before cleaning: ', getConversationCount());
cleanUpOldOpengroups();
+
cleanUpUnusedNodeForKeyEntries();
printDbStats();
@@ -3411,11 +3412,26 @@ function cleanUpOldOpengroups() {
console.info('cleanUpOldOpengroups: ourNumber is not set');
return;
}
+ const pruneSetting = getItemById('prune-setting')?.value;
+
+ if (pruneSetting === undefined) {
+ console.info(
+ 'Prune settings is undefined, skipping cleanUpOldOpengroups but we will need to ask user'
+ );
+ return;
+ }
+
+ if (!pruneSetting) {
+ console.info('Prune setting not enabled, skipping cleanUpOldOpengroups');
+ return;
+ }
+
const v2Convos = getAllOpenGroupV2Conversations();
if (!v2Convos || !v2Convos.length) {
console.info('cleanUpOldOpengroups: v2Convos is empty');
return;
}
+
// For each opengroups, if it has more than 1000 messages, we remove all the messages older than 2 months.
// So this does not limit the size of opengroup history to 1000 messages but to 2 months.
// This is the only way we can cleanup conversations objects from users which just sent messages a while ago and with whom we never interacted.
@@ -3436,20 +3452,20 @@ function cleanUpOldOpengroups() {
if (messagesInConvoBefore >= maxMessagePerOpengroupConvo) {
const minute = 1000 * 60;
const sixMonths = minute * 60 * 24 * 30 * 6;
- const messagesTimestampToRemove = Date.now() - sixMonths;
+ const limitTimestamp = Date.now() - sixMonths;
const countToRemove = assertGlobalInstance()
.prepare(
`SELECT count(*) from ${MESSAGES_TABLE} WHERE serverTimestamp <= $serverTimestamp AND conversationId = $conversationId;`
)
- .get({ conversationId: convoId, serverTimestamp: Date.now() - sixMonths })['count(*)'];
+ .get({ conversationId: convoId, serverTimestamp: limitTimestamp })['count(*)'];
const start = Date.now();
assertGlobalInstance()
.prepare(
`
- DELETE FROM ${MESSAGES_TABLE} WHERE serverTimestamp <= $serverTimestamp AND conversationId = $conversationId`
+ DELETE FROM ${MESSAGES_TABLE} WHERE serverTimestamp <= $serverTimestamp AND conversationId = $conversationId`
)
- .run({ conversationId: convoId, serverTimestamp: messagesTimestampToRemove }); // delete messages older than sixMonths
+ .run({ conversationId: convoId, serverTimestamp: limitTimestamp }); // delete messages older than 6 months ago.
const messagesInConvoAfter = getMessagesCountByConversation(convoId);
console.info(
@@ -3680,6 +3696,7 @@ export const sqlNode = {
getPubkeysInPublicConversation,
getAllGroupsInvolvingId,
removeAllConversations,
+ cleanUpOldOpengroups,
searchConversations,
searchMessages,
diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts
index 412e6c60d..46e26d227 100644
--- a/ts/types/LocalizerKeys.ts
+++ b/ts/types/LocalizerKeys.ts
@@ -7,6 +7,7 @@ export type LocalizerKeys =
| 'startedACall'
| 'mainMenuWindow'
| 'unblocked'
+ | 'keepDisabled'
| 'userAddedToModerators'
| 'to'
| 'sent'
@@ -14,7 +15,9 @@ export type LocalizerKeys =
| 'closedGroupInviteFailMessage'
| 'noContactsForGroup'
| 'linkVisitWarningMessage'
+ | 'messageRequestAcceptedOurs'
| 'anonymous'
+ | 'youHaveANewFriendRequest'
| 'viewMenuZoomOut'
| 'dialogClearAllDataDeletionFailedDesc'
| 'timerOption_10_seconds_abbreviated'
@@ -22,9 +25,11 @@ export type LocalizerKeys =
| 'connectToServerFail'
| 'disableNotifications'
| 'publicChatExists'
+ | 'noMediaUntilApproved'
| 'passwordViewTitle'
| 'joinOpenGroupAfterInvitationConfirmationTitle'
| 'notificationMostRecentFrom'
+ | 'messageRequestAccepted'
| 'timerOption_5_minutes'
| 'linkPreviewsConfirmMessage'
| 'notificationMostRecent'
@@ -44,7 +49,6 @@ export type LocalizerKeys =
| 'viewMenuToggleDevTools'
| 'fileSizeWarning'
| 'openGroupURL'
- | 'hideRequestBannerDescription'
| 'hideMenuBarDescription'
| 'pickClosedGroupMember'
| 'ByUsingThisService...'
@@ -54,6 +58,7 @@ export type LocalizerKeys =
| 'typingAlt'
| 'orJoinOneOfThese'
| 'members'
+ | 'noMessageRequestsPending'
| 'sendRecoveryPhraseMessage'
| 'timerOption_1_hour'
| 'youGotKickedFromGroup'
@@ -68,8 +73,10 @@ export type LocalizerKeys =
| 'addModerators'
| 'sessionMessenger'
| 'today'
+ | 'mustBeApproved'
| 'appMenuHideOthers'
| 'sendFailed'
+ | 'openMessageRequestInbox'
| 'enterPassword'
| 'enterSessionIDOfRecipient'
| 'dialogClearAllDataDeletionFailedMultiple'
@@ -81,6 +88,7 @@ export type LocalizerKeys =
| 'requestsSubtitle'
| 'closedGroupInviteSuccessTitle'
| 'accept'
+ | 'hideBanner'
| 'setPasswordTitle'
| 'editMenuUndo'
| 'pinConversation'
@@ -94,6 +102,7 @@ export type LocalizerKeys =
| 'autoUpdateNewVersionInstructions'
| 'appMenuUnhide'
| 'timerOption_30_minutes_abbreviated'
+ | 'pruneSettingDescription'
| 'voiceMessage'
| 'changePasswordTitle'
| 'copyMessage'
@@ -101,12 +110,11 @@ export type LocalizerKeys =
| 'deleteJustForMe'
| 'changeAccountPasswordTitle'
| 'onionPathIndicatorDescription'
- | 'timestamp_s'
+ | 'pruningOpengroupDialogSubMessage'
| 'mediaPermissionsTitle'
| 'replyingToMessage'
| 'welcomeToYourSession'
| 'editMenuCopy'
- | 'timestamp_m'
| 'leftTheGroup'
| 'timerOption_30_minutes'
| 'nameOnly'
@@ -116,6 +124,7 @@ export type LocalizerKeys =
| 'inviteContacts'
| 'callMediaPermissionsTitle'
| 'blocked'
+ | 'hideRequestBannerDescription'
| 'noBlockedContacts'
| 'leaveGroupConfirmation'
| 'banUserAndDeleteAll'
@@ -197,6 +206,7 @@ export type LocalizerKeys =
| 'viewMenuResetZoom'
| 'startInTrayDescription'
| 'groupNamePlaceholder'
+ | 'messageRequestPending'
| 'stagedPreviewThumbnail'
| 'helpUsTranslateSession'
| 'editMenuDeleteGroup'
@@ -208,6 +218,7 @@ export type LocalizerKeys =
| 'closedGroupInviteFailMessagePlural'
| 'noAudioInputFound'
| 'timerOption_10_seconds'
+ | 'openMessageRequestInboxDescription'
| 'noteToSelf'
| 'failedToAddAsModerator'
| 'disabledDisappearingMessages'
@@ -219,7 +230,6 @@ export type LocalizerKeys =
| 'displayName'
| 'invalidSessionId'
| 'audioPermissionNeeded'
- | 'timestamp_h'
| 'add'
| 'messageRequests'
| 'show'
@@ -228,6 +238,7 @@ export type LocalizerKeys =
| 'goToSupportPage'
| 'passwordsDoNotMatch'
| 'createClosedGroupNamePrompt'
+ | 'pruningOpengroupDialogMessage'
| 'audioMessageAutoplayDescription'
| 'leaveAndRemoveForEveryone'
| 'previewThumbnail'
@@ -236,9 +247,9 @@ export type LocalizerKeys =
| 'editMenuDeleteContact'
| 'hideMenuBarTitle'
| 'imageCaptionIconAlt'
- | 'clearAll'
| 'sendRecoveryPhraseTitle'
| 'multipleJoinedTheGroup'
+ | 'messageRequestAcceptedOursNoName'
| 'databaseError'
| 'resend'
| 'copiedToClipboard'
@@ -248,6 +259,7 @@ export type LocalizerKeys =
| 'unableToLoadAttachment'
| 'cameraPermissionNeededTitle'
| 'editMenuRedo'
+ | 'hideRequestBanner'
| 'changeNicknameMessage'
| 'close'
| 'deleteMessageQuestion'
@@ -258,6 +270,7 @@ export type LocalizerKeys =
| 'getStarted'
| 'unblockUser'
| 'blockUser'
+ | 'clearAllConfirmationTitle'
| 'trustThisContactDialogTitle'
| 'received'
| 'trimDatabaseConfirmationBody'
@@ -276,6 +289,7 @@ export type LocalizerKeys =
| 'timerOption_6_hours_abbreviated'
| 'timerOption_1_week_abbreviated'
| 'timerSetTo'
+ | 'enable'
| 'notificationSubtitle'
| 'youChangedTheTimer'
| 'updatedTheGroup'
@@ -288,8 +302,10 @@ export type LocalizerKeys =
| 'noSearchResults'
| 'changeNickname'
| 'userUnbanned'
+ | 'respondingToRequestWarning'
| 'error'
| 'clearAllData'
+ | 'pruningOpengroupDialogTitle'
| 'contactAvatarAlt'
| 'disappearingMessages'
| 'autoUpdateNewVersionTitle'
@@ -328,6 +344,7 @@ export type LocalizerKeys =
| 'markAllAsRead'
| 'failedResolveOns'
| 'showDebugLog'
+ | 'declineRequestMessage'
| 'autoUpdateDownloadButtonLabel'
| 'dialogClearAllDataDeletionFailedTitleQuestion'
| 'autoUpdateDownloadInstructions'
@@ -353,6 +370,7 @@ export type LocalizerKeys =
| 'learnMore'
| 'passwordCharacterError'
| 'autoUpdateSettingTitle'
+ | 'documentsEmptyState'
| 'deleteForEveryone'
| 'createSessionID'
| 'multipleLeftTheGroup'
@@ -376,9 +394,11 @@ export type LocalizerKeys =
| 'callMediaPermissionsDialogContent'
| 'timerOption_1_day_abbreviated'
| 'about'
+ | 'clearAllConfirmationBody'
| 'ok'
| 'multipleKickedFromTheGroup'
| 'trimDatabase'
+ | 'clearAll'
| 'recoveryPhraseSavePromptMain'
| 'editMenuPaste'
| 'areYouSureDeleteDeviceOnly'
@@ -421,7 +441,7 @@ export type LocalizerKeys =
| 'recoveryPhrase'
| 'newMessages'
| 'you'
- | 'documentsEmptyState'
+ | 'pruneSettingTitle'
| 'unbanUser'
| 'notificationForConvo_mentions_only'
| 'trustThisContactDialogDescription'
@@ -429,22 +449,4 @@ export type LocalizerKeys =
| 'searchFor...'
| 'joinedTheGroup'
| 'editGroupName'
- | 'trimDatabase'
- | 'trimDatabaseDescription'
- | 'trimDatabaseConfirmationBody'
- | 'respondingToRequestWarning'
- | 'messageRequestPending'
- | 'messageRequestAccepted'
- | 'messageRequestAcceptedOurs'
- | 'messageRequestAcceptedOursNoName'
- | 'declineRequestMessage'
- | 'openMessageRequestInbox'
- | 'hideRequestBanner'
- | 'noMessageRequestsPending'
- | 'noMediaUntilApproved'
- | 'mustBeApproved'
- | 'youHaveANewFriendRequest'
- | 'clearAllConfirmationTitle'
- | 'clearAllConfirmationBody'
- | 'hideBanner'
| 'reportIssue';
diff --git a/ts/util/accountManager.ts b/ts/util/accountManager.ts
index c662b86cb..a24d74732 100644
--- a/ts/util/accountManager.ts
+++ b/ts/util/accountManager.ts
@@ -164,6 +164,10 @@ async function createAccount(identityKeyPair: any) {
// Enable typing indicators by default
await Storage.put(SettingsKey.settingsTypingIndicator, false);
+ // opengroups pruning in ON by default on new accounts, but you can change that from the settings
+ await Storage.put(SettingsKey.settingsOpengroupPruning, true);
+ await window.setOpengroupPruning(true);
+
await setLocalPubKey(pubKeyString);
}
diff --git a/ts/window.d.ts b/ts/window.d.ts
index df99dc20e..a9a8e82b0 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -91,6 +91,8 @@ declare global {
confirmationDialog: any;
setStartInTray: (val: boolean) => Promise;
getStartInTray: () => Promise;
+ getOpengroupPruning: () => Promise;
+ setOpengroupPruning: (val: boolean) => Promise;
closeAbout: () => void;
getAutoUpdateEnabled: () => boolean;
setAutoUpdateEnabled: (enabled: boolean) => void;