Add dev menu to import local backup

This commit is contained in:
ayumi-signal 2025-05-19 16:32:06 -07:00 committed by GitHub
parent efa9102a1b
commit c10d59458f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 41 additions and 89 deletions

View file

@ -287,6 +287,10 @@
"messageformat": "Set Up as Standalone Device", "messageformat": "Set Up as Standalone Device",
"description": "Only available on development modes, menu option to open up the standalone device setup sequence" "description": "Only available on development modes, menu option to open up the standalone device setup sequence"
}, },
"icu:menuStageLocalBackupForImport": {
"messageformat": "Stage Local Backup for Import…",
"description": "Only available on development modes before linking. Menu option to open file browser to pick a local backup folder for import during link."
},
"icu:messageContextMenuButton": { "icu:messageContextMenuButton": {
"messageformat": "More actions", "messageformat": "More actions",
"description": "Label for context button next to each message" "description": "Label for context button next to each message"
@ -6660,14 +6664,6 @@
"messageformat": "Export local encrypted backup to a folder and validate it", "messageformat": "Export local encrypted backup to a folder and validate it",
"description": "Description of the internal local backup export tool" "description": "Description of the internal local backup export tool"
}, },
"icu:Preferences__internal__import-local-backup": {
"messageformat": "Import…",
"description": "Button to run internal local backup import tool"
},
"icu:Preferences__internal__import-local-backup--description": {
"messageformat": "Stage a local encrypted backup for import on link",
"description": "Description of the internal local backup export tool"
},
"icu:Preferences__internal__validate-backup--description": { "icu:Preferences__internal__validate-backup--description": {
"messageformat": "Export encrypted backup to memory and run validation suite on it", "messageformat": "Export encrypted backup to memory and run validation suite on it",
"description": "Description of the internal backup validation tool" "description": "Description of the internal backup validation tool"

View file

@ -100,7 +100,7 @@ import type { CreateTemplateOptionsType } from './menu';
import { createTemplate } from './menu'; import { createTemplate } from './menu';
import { installFileHandler, installWebHandler } from './protocol_filter'; import { installFileHandler, installWebHandler } from './protocol_filter';
import OS from '../ts/util/os/osMain'; import OS from '../ts/util/os/osMain';
import { isProduction } from '../ts/util/version'; import { isNightly, isProduction } from '../ts/util/version';
import { clearTimeoutIfNecessary } from '../ts/util/clearTimeoutIfNecessary'; import { clearTimeoutIfNecessary } from '../ts/util/clearTimeoutIfNecessary';
import { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow'; import { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow';
import { ChallengeMainHandler } from '../ts/main/challengeMain'; import { ChallengeMainHandler } from '../ts/main/challengeMain';
@ -1259,6 +1259,12 @@ function setupAsStandalone() {
} }
} }
function stageLocalBackupForImport() {
if (mainWindow) {
mainWindow.webContents.send('stage-local-backup-for-import');
}
}
let screenShareWindow: BrowserWindow | undefined; let screenShareWindow: BrowserWindow | undefined;
async function showScreenShareWindow(sourceName: string | undefined) { async function showScreenShareWindow(sourceName: string | undefined) {
if (screenShareWindow) { if (screenShareWindow) {
@ -2309,12 +2315,14 @@ app.on('ready', async () => {
function setupMenu(options?: Partial<CreateTemplateOptionsType>) { function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
const { platform } = process; const { platform } = process;
const version = app.getVersion();
menuOptions = { menuOptions = {
// options // options
development, development,
devTools: defaultWebPrefs.devTools, devTools: defaultWebPrefs.devTools,
includeSetup: false, includeSetup: false,
isProduction: isProduction(app.getVersion()), isNightly: isNightly(version),
isProduction: isProduction(version),
platform, platform,
// actions // actions
@ -2327,6 +2335,7 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
openSupportPage, openSupportPage,
setupAsNewDevice, setupAsNewDevice,
setupAsStandalone, setupAsStandalone,
stageLocalBackupForImport,
showAbout, showAbout,
showDebugLog: showDebugLogWindow, showDebugLog: showDebugLogWindow,
showCallingDevTools: showCallingDevToolsWindow, showCallingDevTools: showCallingDevToolsWindow,
@ -2359,6 +2368,7 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
development: menuOptions.development, development: menuOptions.development,
devTools: menuOptions.devTools, devTools: menuOptions.devTools,
includeSetup: menuOptions.includeSetup, includeSetup: menuOptions.includeSetup,
isNightly: menuOptions.isNightly,
isProduction: menuOptions.isProduction, isProduction: menuOptions.isProduction,
platform: menuOptions.platform, platform: menuOptions.platform,
}); });
@ -3198,6 +3208,7 @@ ipc.handle('getMenuOptions', async () => {
development: menuOptions?.development ?? false, development: menuOptions?.development ?? false,
devTools: menuOptions?.devTools ?? false, devTools: menuOptions?.devTools ?? false,
includeSetup: menuOptions?.includeSetup ?? false, includeSetup: menuOptions?.includeSetup ?? false,
isNightly: menuOptions?.isNightly ?? false,
isProduction: menuOptions?.isProduction ?? true, isProduction: menuOptions?.isProduction ?? true,
platform: menuOptions?.platform ?? 'unknown', platform: menuOptions?.platform ?? 'unknown',
}; };

View file

@ -32,6 +32,7 @@ export const createTemplate = (
platform, platform,
setupAsNewDevice, setupAsNewDevice,
setupAsStandalone, setupAsStandalone,
stageLocalBackupForImport,
forceUpdate, forceUpdate,
showAbout, showAbout,
showDebugLog, showDebugLog,
@ -225,6 +226,13 @@ export const createTemplate = (
if (Array.isArray(fileMenu.submenu)) { if (Array.isArray(fileMenu.submenu)) {
// These are in reverse order, since we're prepending them one at a time // These are in reverse order, since we're prepending them one at a time
if (options.isNightly) {
fileMenu.submenu.unshift({
label: i18n('icu:menuStageLocalBackupForImport'),
click: stageLocalBackupForImport,
});
}
if (options.development) { if (options.development) {
fileMenu.submenu.unshift({ fileMenu.submenu.unshift({
label: i18n('icu:menuSetupAsStandalone'), label: i18n('icu:menuSetupAsStandalone'),

View file

@ -1357,6 +1357,10 @@ export async function startApp(): Promise<void> {
window.reduxActions.app.openStandalone(); window.reduxActions.app.openStandalone();
}); });
window.Whisper.events.on('stageLocalBackupForImport', () => {
drop(backupsService._internalStageLocalBackupForImport());
});
window.Whisper.events.on('openSettingsTab', () => { window.Whisper.events.on('openSettingsTab', () => {
window.reduxActions.nav.changeNavTab(NavTab.Settings); window.reduxActions.nav.changeNavTab(NavTab.Settings);
}); });

View file

@ -200,13 +200,6 @@ export default {
result: exportLocalBackupResult, result: exportLocalBackupResult,
}; };
}, },
importLocalBackup: async () => {
return {
success: true,
error: undefined,
snapshotDir: exportLocalBackupResult.snapshotDir,
};
},
makeSyncRequest: action('makeSyncRequest'), makeSyncRequest: action('makeSyncRequest'),
onAudioNotificationsChange: action('onAudioNotificationsChange'), onAudioNotificationsChange: action('onAudioNotificationsChange'),
onAutoConvertEmojiChange: action('onAutoConvertEmojiChange'), onAutoConvertEmojiChange: action('onAutoConvertEmojiChange'),

View file

@ -73,7 +73,6 @@ import {
import { PreferencesBackups } from './PreferencesBackups'; import { PreferencesBackups } from './PreferencesBackups';
import { PreferencesInternal } from './PreferencesInternal'; import { PreferencesInternal } from './PreferencesInternal';
import { FunEmojiLocalizationProvider } from './fun/FunEmojiLocalizationProvider'; import { FunEmojiLocalizationProvider } from './fun/FunEmojiLocalizationProvider';
import type { ValidateLocalBackupStructureResultType } from '../services/backups/util/localBackup';
import { NavTabsToggle } from './NavTabs'; import { NavTabsToggle } from './NavTabs';
import type { UnreadStats } from '../util/countUnreadStats'; import type { UnreadStats } from '../util/countUnreadStats';
@ -173,7 +172,6 @@ type PropsFunctionType = {
editCustomColor: (colorId: string, color: CustomColorType) => unknown; editCustomColor: (colorId: string, color: CustomColorType) => unknown;
exportLocalBackup: () => Promise<BackupValidationResultType>; exportLocalBackup: () => Promise<BackupValidationResultType>;
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>; getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
importLocalBackup: () => Promise<ValidateLocalBackupStructureResultType>;
makeSyncRequest: () => unknown; makeSyncRequest: () => unknown;
onStartUpdate: () => unknown; onStartUpdate: () => unknown;
refreshCloudBackupStatus: () => void; refreshCloudBackupStatus: () => void;
@ -330,7 +328,6 @@ export function Preferences({
hasTextFormatting, hasTextFormatting,
hasTypingIndicators, hasTypingIndicators,
i18n, i18n,
importLocalBackup,
initialPage = Page.General, initialPage = Page.General,
initialSpellCheckSetting, initialSpellCheckSetting,
isAutoDownloadUpdatesSupported, isAutoDownloadUpdatesSupported,
@ -1836,7 +1833,6 @@ export function Preferences({
<PreferencesInternal <PreferencesInternal
i18n={i18n} i18n={i18n}
exportLocalBackup={exportLocalBackup} exportLocalBackup={exportLocalBackup}
importLocalBackup={importLocalBackup}
validateBackup={validateBackup} validateBackup={validateBackup}
/> />
); );

View file

@ -7,7 +7,6 @@ import { toLogFormat } from '../types/errors';
import { formatFileSize } from '../util/formatFileSize'; import { formatFileSize } from '../util/formatFileSize';
import { SECOND } from '../util/durations'; import { SECOND } from '../util/durations';
import type { ValidationResultType as BackupValidationResultType } from '../services/backups'; import type { ValidationResultType as BackupValidationResultType } from '../services/backups';
import type { ValidateLocalBackupStructureResultType } from '../services/backups/util/localBackup';
import { SettingsRow, SettingsControl } from './PreferencesUtil'; import { SettingsRow, SettingsControl } from './PreferencesUtil';
import { Button, ButtonVariant } from './Button'; import { Button, ButtonVariant } from './Button';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
@ -15,21 +14,16 @@ import { Spinner } from './Spinner';
export function PreferencesInternal({ export function PreferencesInternal({
i18n, i18n,
exportLocalBackup: doExportLocalBackup, exportLocalBackup: doExportLocalBackup,
importLocalBackup: doImportLocalBackup,
validateBackup: doValidateBackup, validateBackup: doValidateBackup,
}: { }: {
i18n: LocalizerType; i18n: LocalizerType;
exportLocalBackup: () => Promise<BackupValidationResultType>; exportLocalBackup: () => Promise<BackupValidationResultType>;
importLocalBackup: () => Promise<ValidateLocalBackupStructureResultType>;
validateBackup: () => Promise<BackupValidationResultType>; validateBackup: () => Promise<BackupValidationResultType>;
}): JSX.Element { }): JSX.Element {
const [isExportPending, setIsExportPending] = useState(false); const [isExportPending, setIsExportPending] = useState(false);
const [exportResult, setExportResult] = useState< const [exportResult, setExportResult] = useState<
BackupValidationResultType | undefined BackupValidationResultType | undefined
>(); >();
const [importResult, setImportResult] = useState<
ValidateLocalBackupStructureResultType | undefined
>();
const [isValidationPending, setIsValidationPending] = useState(false); const [isValidationPending, setIsValidationPending] = useState(false);
const [validationResult, setValidationResult] = useState< const [validationResult, setValidationResult] = useState<
@ -110,49 +104,6 @@ export function PreferencesInternal({
} }
}, [doExportLocalBackup]); }, [doExportLocalBackup]);
const importLocalBackup = useCallback(async () => {
setImportResult(undefined);
try {
setImportResult(await doImportLocalBackup());
} catch (error) {
setImportResult({
success: false,
error: toLogFormat(error),
snapshotDir: undefined,
});
}
}, [doImportLocalBackup]);
const renderImportResult = useCallback(
(
didImportResult: ValidateLocalBackupStructureResultType | undefined
): JSX.Element | undefined => {
if (didImportResult == null) {
return;
}
const { success, error, snapshotDir } = didImportResult;
if (success) {
return (
<div className="Preferences--internal--validate-backup--result">
<pre>
<code>{`Staged: ${snapshotDir}\n\nPlease link to finish import.`}</code>
</pre>
</div>
);
}
return (
<div className="Preferences--internal--validate-backup--error">
<pre>
<code>{`Failed: ${error}`}</code>
</pre>
</div>
);
},
[]
);
return ( return (
<> <>
<div className="Preferences__title Preferences__title--internal"> <div className="Preferences__title Preferences__title--internal">
@ -209,22 +160,6 @@ export function PreferencesInternal({
/> />
{renderValidationResult(exportResult)} {renderValidationResult(exportResult)}
<SettingsControl
left={i18n(
'icu:Preferences__internal__import-local-backup--description'
)}
right={
<Button
variant={ButtonVariant.Secondary}
onClick={importLocalBackup}
>
{i18n('icu:Preferences__internal__import-local-backup')}
</Button>
}
/>
{renderImportResult(importResult)}
</SettingsRow> </SettingsRow>
</> </>
); );

View file

@ -148,6 +148,7 @@ export function getEmptyState(): UserStateType {
development: false, development: false,
devTools: false, devTools: false,
includeSetup: false, includeSetup: false,
isNightly: false,
isProduction: true, isProduction: true,
platform: 'unknown', platform: 'unknown',
}, },

View file

@ -151,8 +151,6 @@ export function SmartPreferences(): JSX.Element {
const validateBackup = () => backupsService._internalValidate(); const validateBackup = () => backupsService._internalValidate();
const exportLocalBackup = () => backupsService._internalExportLocalBackup(); const exportLocalBackup = () => backupsService._internalExportLocalBackup();
const importLocalBackup = () =>
backupsService._internalStageLocalBackupForImport();
const doDeleteAllData = () => renderClearingDataView(); const doDeleteAllData = () => renderClearingDataView();
const refreshCloudBackupStatus = const refreshCloudBackupStatus =
window.Signal.Services.backups.throttledFetchCloudBackupStatus; window.Signal.Services.backups.throttledFetchCloudBackupStatus;
@ -633,7 +631,6 @@ export function SmartPreferences(): JSX.Element {
hasTextFormatting={hasTextFormatting} hasTextFormatting={hasTextFormatting}
hasTypingIndicators={hasTypingIndicators} hasTypingIndicators={hasTypingIndicators}
i18n={i18n} i18n={i18n}
importLocalBackup={importLocalBackup}
initialSpellCheckSetting={initialSpellCheckSetting} initialSpellCheckSetting={initialSpellCheckSetting}
isAutoDownloadUpdatesSupported={isAutoDownloadUpdatesSupported} isAutoDownloadUpdatesSupported={isAutoDownloadUpdatesSupported}
isAutoLaunchSupported={isAutoLaunchSupported} isAutoLaunchSupported={isAutoLaunchSupported}

View file

@ -26,6 +26,7 @@ const showCallingDevTools = stub();
const showKeyboardShortcuts = stub(); const showKeyboardShortcuts = stub();
const showSettings = stub(); const showSettings = stub();
const showWindow = stub(); const showWindow = stub();
const stageLocalBackupForImport = stub();
const zoomIn = stub(); const zoomIn = stub();
const zoomOut = stub(); const zoomOut = stub();
const zoomReset = stub(); const zoomReset = stub();
@ -233,6 +234,7 @@ describe('createTemplate', () => {
showKeyboardShortcuts, showKeyboardShortcuts,
showSettings, showSettings,
showWindow, showWindow,
stageLocalBackupForImport,
zoomIn, zoomIn,
zoomOut, zoomOut,
zoomReset, zoomReset,
@ -245,6 +247,7 @@ describe('createTemplate', () => {
development: false, development: false,
devTools: true, devTools: true,
includeSetup: false, includeSetup: false,
isNightly: false,
isProduction: true, isProduction: true,
platform, platform,
...actions, ...actions,
@ -259,6 +262,7 @@ describe('createTemplate', () => {
development: false, development: false,
devTools: true, devTools: true,
includeSetup: true, includeSetup: true,
isNightly: false,
isProduction: true, isProduction: true,
platform, platform,
...actions, ...actions,

View file

@ -9,6 +9,7 @@ export type MenuOptionsType = Readonly<{
development: boolean; development: boolean;
devTools: boolean; devTools: boolean;
includeSetup: boolean; includeSetup: boolean;
isNightly: boolean;
isProduction: boolean; isProduction: boolean;
platform: string; platform: string;
}>; }>;
@ -29,6 +30,7 @@ export type MenuActionsType = Readonly<{
showKeyboardShortcuts: () => unknown; showKeyboardShortcuts: () => unknown;
showSettings: () => unknown; showSettings: () => unknown;
showWindow: () => unknown; showWindow: () => unknown;
stageLocalBackupForImport: () => unknown;
zoomIn: () => unknown; zoomIn: () => unknown;
zoomOut: () => unknown; zoomOut: () => unknown;
zoomReset: () => unknown; zoomReset: () => unknown;

View file

@ -279,6 +279,10 @@ ipc.on('set-up-as-standalone', () => {
window.Whisper.events.trigger('setupAsStandalone'); window.Whisper.events.trigger('setupAsStandalone');
}); });
ipc.on('stage-local-backup-for-import', () => {
window.Whisper.events.trigger('stageLocalBackupForImport');
});
ipc.on('challenge:response', (_event, response) => { ipc.on('challenge:response', (_event, response) => {
window.Whisper.events.trigger('challengeResponse', response); window.Whisper.events.trigger('challengeResponse', response);
}); });

View file

@ -115,6 +115,7 @@ window.testUtilities = {
development: false, development: false,
devTools: false, devTools: false,
includeSetup: false, includeSetup: false,
isNightly: false,
isProduction: false, isProduction: false,
platform: 'test', platform: 'test',
}, },