From c10d59458f734e9d455596ed5f73dff82f1358e5 Mon Sep 17 00:00:00 2001 From: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com> Date: Mon, 19 May 2025 16:32:06 -0700 Subject: [PATCH] Add dev menu to import local backup --- _locales/en/messages.json | 12 ++--- app/main.ts | 15 ++++++- app/menu.ts | 8 ++++ ts/background.ts | 4 ++ ts/components/Preferences.stories.tsx | 7 --- ts/components/Preferences.tsx | 4 -- ts/components/PreferencesInternal.tsx | 65 --------------------------- ts/state/ducks/user.ts | 1 + ts/state/smart/Preferences.tsx | 3 -- ts/test-node/app/menu_test.ts | 4 ++ ts/types/menu.ts | 2 + ts/windows/main/phase1-ipc.ts | 4 ++ ts/windows/main/preload_test.ts | 1 + 13 files changed, 41 insertions(+), 89 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index c0c33be1cb..bd65804e38 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -287,6 +287,10 @@ "messageformat": "Set Up as Standalone Device", "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": { "messageformat": "More actions", "description": "Label for context button next to each message" @@ -6660,14 +6664,6 @@ "messageformat": "Export local encrypted backup to a folder and validate it", "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": { "messageformat": "Export encrypted backup to memory and run validation suite on it", "description": "Description of the internal backup validation tool" diff --git a/app/main.ts b/app/main.ts index 818f533ef8..c1a1a841a0 100644 --- a/app/main.ts +++ b/app/main.ts @@ -100,7 +100,7 @@ import type { CreateTemplateOptionsType } from './menu'; import { createTemplate } from './menu'; import { installFileHandler, installWebHandler } from './protocol_filter'; 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 { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow'; 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; async function showScreenShareWindow(sourceName: string | undefined) { if (screenShareWindow) { @@ -2309,12 +2315,14 @@ app.on('ready', async () => { function setupMenu(options?: Partial) { const { platform } = process; + const version = app.getVersion(); menuOptions = { // options development, devTools: defaultWebPrefs.devTools, includeSetup: false, - isProduction: isProduction(app.getVersion()), + isNightly: isNightly(version), + isProduction: isProduction(version), platform, // actions @@ -2327,6 +2335,7 @@ function setupMenu(options?: Partial) { openSupportPage, setupAsNewDevice, setupAsStandalone, + stageLocalBackupForImport, showAbout, showDebugLog: showDebugLogWindow, showCallingDevTools: showCallingDevToolsWindow, @@ -2359,6 +2368,7 @@ function setupMenu(options?: Partial) { development: menuOptions.development, devTools: menuOptions.devTools, includeSetup: menuOptions.includeSetup, + isNightly: menuOptions.isNightly, isProduction: menuOptions.isProduction, platform: menuOptions.platform, }); @@ -3198,6 +3208,7 @@ ipc.handle('getMenuOptions', async () => { development: menuOptions?.development ?? false, devTools: menuOptions?.devTools ?? false, includeSetup: menuOptions?.includeSetup ?? false, + isNightly: menuOptions?.isNightly ?? false, isProduction: menuOptions?.isProduction ?? true, platform: menuOptions?.platform ?? 'unknown', }; diff --git a/app/menu.ts b/app/menu.ts index e52d8494e4..c67d4c615a 100644 --- a/app/menu.ts +++ b/app/menu.ts @@ -32,6 +32,7 @@ export const createTemplate = ( platform, setupAsNewDevice, setupAsStandalone, + stageLocalBackupForImport, forceUpdate, showAbout, showDebugLog, @@ -225,6 +226,13 @@ export const createTemplate = ( if (Array.isArray(fileMenu.submenu)) { // 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) { fileMenu.submenu.unshift({ label: i18n('icu:menuSetupAsStandalone'), diff --git a/ts/background.ts b/ts/background.ts index d74e114907..3d12bcdf0d 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -1357,6 +1357,10 @@ export async function startApp(): Promise { window.reduxActions.app.openStandalone(); }); + window.Whisper.events.on('stageLocalBackupForImport', () => { + drop(backupsService._internalStageLocalBackupForImport()); + }); + window.Whisper.events.on('openSettingsTab', () => { window.reduxActions.nav.changeNavTab(NavTab.Settings); }); diff --git a/ts/components/Preferences.stories.tsx b/ts/components/Preferences.stories.tsx index 6cf13a97a1..51d1282a7b 100644 --- a/ts/components/Preferences.stories.tsx +++ b/ts/components/Preferences.stories.tsx @@ -200,13 +200,6 @@ export default { result: exportLocalBackupResult, }; }, - importLocalBackup: async () => { - return { - success: true, - error: undefined, - snapshotDir: exportLocalBackupResult.snapshotDir, - }; - }, makeSyncRequest: action('makeSyncRequest'), onAudioNotificationsChange: action('onAudioNotificationsChange'), onAutoConvertEmojiChange: action('onAutoConvertEmojiChange'), diff --git a/ts/components/Preferences.tsx b/ts/components/Preferences.tsx index 8023787578..b03efbb19e 100644 --- a/ts/components/Preferences.tsx +++ b/ts/components/Preferences.tsx @@ -73,7 +73,6 @@ import { import { PreferencesBackups } from './PreferencesBackups'; import { PreferencesInternal } from './PreferencesInternal'; import { FunEmojiLocalizationProvider } from './fun/FunEmojiLocalizationProvider'; -import type { ValidateLocalBackupStructureResultType } from '../services/backups/util/localBackup'; import { NavTabsToggle } from './NavTabs'; import type { UnreadStats } from '../util/countUnreadStats'; @@ -173,7 +172,6 @@ type PropsFunctionType = { editCustomColor: (colorId: string, color: CustomColorType) => unknown; exportLocalBackup: () => Promise; getConversationsWithCustomColor: (colorId: string) => Array; - importLocalBackup: () => Promise; makeSyncRequest: () => unknown; onStartUpdate: () => unknown; refreshCloudBackupStatus: () => void; @@ -330,7 +328,6 @@ export function Preferences({ hasTextFormatting, hasTypingIndicators, i18n, - importLocalBackup, initialPage = Page.General, initialSpellCheckSetting, isAutoDownloadUpdatesSupported, @@ -1836,7 +1833,6 @@ export function Preferences({ ); diff --git a/ts/components/PreferencesInternal.tsx b/ts/components/PreferencesInternal.tsx index 8cfe39e024..b71b47c30c 100644 --- a/ts/components/PreferencesInternal.tsx +++ b/ts/components/PreferencesInternal.tsx @@ -7,7 +7,6 @@ import { toLogFormat } from '../types/errors'; import { formatFileSize } from '../util/formatFileSize'; import { SECOND } from '../util/durations'; import type { ValidationResultType as BackupValidationResultType } from '../services/backups'; -import type { ValidateLocalBackupStructureResultType } from '../services/backups/util/localBackup'; import { SettingsRow, SettingsControl } from './PreferencesUtil'; import { Button, ButtonVariant } from './Button'; import { Spinner } from './Spinner'; @@ -15,21 +14,16 @@ import { Spinner } from './Spinner'; export function PreferencesInternal({ i18n, exportLocalBackup: doExportLocalBackup, - importLocalBackup: doImportLocalBackup, validateBackup: doValidateBackup, }: { i18n: LocalizerType; exportLocalBackup: () => Promise; - importLocalBackup: () => Promise; validateBackup: () => Promise; }): JSX.Element { const [isExportPending, setIsExportPending] = useState(false); const [exportResult, setExportResult] = useState< BackupValidationResultType | undefined >(); - const [importResult, setImportResult] = useState< - ValidateLocalBackupStructureResultType | undefined - >(); const [isValidationPending, setIsValidationPending] = useState(false); const [validationResult, setValidationResult] = useState< @@ -110,49 +104,6 @@ export function PreferencesInternal({ } }, [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 ( -
-
-              {`Staged: ${snapshotDir}\n\nPlease link to finish import.`}
-            
-
- ); - } - - return ( -
-
-            {`Failed: ${error}`}
-          
-
- ); - }, - [] - ); - return ( <>
@@ -209,22 +160,6 @@ export function PreferencesInternal({ /> {renderValidationResult(exportResult)} - - - {i18n('icu:Preferences__internal__import-local-backup')} - - } - /> - - {renderImportResult(importResult)} ); diff --git a/ts/state/ducks/user.ts b/ts/state/ducks/user.ts index 20535dc02b..6d5797e8c8 100644 --- a/ts/state/ducks/user.ts +++ b/ts/state/ducks/user.ts @@ -148,6 +148,7 @@ export function getEmptyState(): UserStateType { development: false, devTools: false, includeSetup: false, + isNightly: false, isProduction: true, platform: 'unknown', }, diff --git a/ts/state/smart/Preferences.tsx b/ts/state/smart/Preferences.tsx index 2a9cefddf9..469a93a300 100644 --- a/ts/state/smart/Preferences.tsx +++ b/ts/state/smart/Preferences.tsx @@ -151,8 +151,6 @@ export function SmartPreferences(): JSX.Element { const validateBackup = () => backupsService._internalValidate(); const exportLocalBackup = () => backupsService._internalExportLocalBackup(); - const importLocalBackup = () => - backupsService._internalStageLocalBackupForImport(); const doDeleteAllData = () => renderClearingDataView(); const refreshCloudBackupStatus = window.Signal.Services.backups.throttledFetchCloudBackupStatus; @@ -633,7 +631,6 @@ export function SmartPreferences(): JSX.Element { hasTextFormatting={hasTextFormatting} hasTypingIndicators={hasTypingIndicators} i18n={i18n} - importLocalBackup={importLocalBackup} initialSpellCheckSetting={initialSpellCheckSetting} isAutoDownloadUpdatesSupported={isAutoDownloadUpdatesSupported} isAutoLaunchSupported={isAutoLaunchSupported} diff --git a/ts/test-node/app/menu_test.ts b/ts/test-node/app/menu_test.ts index 929c1831c4..12cc5c4732 100644 --- a/ts/test-node/app/menu_test.ts +++ b/ts/test-node/app/menu_test.ts @@ -26,6 +26,7 @@ const showCallingDevTools = stub(); const showKeyboardShortcuts = stub(); const showSettings = stub(); const showWindow = stub(); +const stageLocalBackupForImport = stub(); const zoomIn = stub(); const zoomOut = stub(); const zoomReset = stub(); @@ -233,6 +234,7 @@ describe('createTemplate', () => { showKeyboardShortcuts, showSettings, showWindow, + stageLocalBackupForImport, zoomIn, zoomOut, zoomReset, @@ -245,6 +247,7 @@ describe('createTemplate', () => { development: false, devTools: true, includeSetup: false, + isNightly: false, isProduction: true, platform, ...actions, @@ -259,6 +262,7 @@ describe('createTemplate', () => { development: false, devTools: true, includeSetup: true, + isNightly: false, isProduction: true, platform, ...actions, diff --git a/ts/types/menu.ts b/ts/types/menu.ts index 4196859cee..b436bab199 100644 --- a/ts/types/menu.ts +++ b/ts/types/menu.ts @@ -9,6 +9,7 @@ export type MenuOptionsType = Readonly<{ development: boolean; devTools: boolean; includeSetup: boolean; + isNightly: boolean; isProduction: boolean; platform: string; }>; @@ -29,6 +30,7 @@ export type MenuActionsType = Readonly<{ showKeyboardShortcuts: () => unknown; showSettings: () => unknown; showWindow: () => unknown; + stageLocalBackupForImport: () => unknown; zoomIn: () => unknown; zoomOut: () => unknown; zoomReset: () => unknown; diff --git a/ts/windows/main/phase1-ipc.ts b/ts/windows/main/phase1-ipc.ts index 899dfe6362..1c5ee26884 100644 --- a/ts/windows/main/phase1-ipc.ts +++ b/ts/windows/main/phase1-ipc.ts @@ -279,6 +279,10 @@ ipc.on('set-up-as-standalone', () => { window.Whisper.events.trigger('setupAsStandalone'); }); +ipc.on('stage-local-backup-for-import', () => { + window.Whisper.events.trigger('stageLocalBackupForImport'); +}); + ipc.on('challenge:response', (_event, response) => { window.Whisper.events.trigger('challengeResponse', response); }); diff --git a/ts/windows/main/preload_test.ts b/ts/windows/main/preload_test.ts index ea93fb8e46..f5800ccf6b 100644 --- a/ts/windows/main/preload_test.ts +++ b/ts/windows/main/preload_test.ts @@ -115,6 +115,7 @@ window.testUtilities = { development: false, devTools: false, includeSetup: false, + isNightly: false, isProduction: false, platform: 'test', },