feat: Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. (#2322)

This commit is contained in:
Mo
2023-05-02 11:05:10 -05:00
committed by GitHub
parent 3df23cdb5c
commit 7e3db49322
76 changed files with 1526 additions and 1013 deletions

View File

@@ -3,52 +3,22 @@ import { Component } from '../Main/Packages/PackageManagerInterface'
export interface CrossProcessBridge extends FileBackupsDevice {
get extServerHost(): string
get useNativeKeychain(): boolean
get rendererPath(): string
get isMacOS(): boolean
get appVersion(): string
get useSystemMenuBar(): boolean
closeWindow(): void
minimizeWindow(): void
maximizeWindow(): void
unmaximizeWindow(): void
isWindowMaximized(): boolean
getKeychainValue(): Promise<unknown>
setKeychainValue: (value: unknown) => Promise<void>
clearKeychainValue(): Promise<boolean>
localBackupsCount(): Promise<number>
viewlocalBackups(): void
deleteLocalBackups(): Promise<void>
saveDataBackup(data: unknown): void
displayAppMenu(): void
syncComponents(components: Component[]): void
onMajorDataChange(): void
onSearch(text: string): void
onInitialDataLoad(): void
destroyAllData(): void
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean>
}

View File

@@ -1,11 +1,11 @@
import {
DesktopDeviceInterface,
Environment,
FileBackupsMapping,
RawKeychainValue,
FileBackupRecord,
FileBackupReadToken,
FileBackupReadChunkResponse,
FileBackupsMapping,
PlaintextBackupsMapping,
} from '@web/Application/Device/DesktopSnjsExports'
import { WebOrDesktopDevice } from '@web/Application/Device/WebOrDesktopDevice'
import { Component } from '../Main/Packages/PackageManagerInterface'
@@ -25,6 +25,33 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
super(appVersion)
}
openLocation(path: string): Promise<void> {
return this.remoteBridge.openLocation(path)
}
presentDirectoryPickerForLocationChangeAndTransferOld(
appendPath: string,
oldLocation?: string | undefined,
): Promise<string | undefined> {
return this.remoteBridge.presentDirectoryPickerForLocationChangeAndTransferOld(appendPath, oldLocation)
}
getFilesBackupsMappingFile(location: string): Promise<FileBackupsMapping> {
return this.remoteBridge.getFilesBackupsMappingFile(location)
}
getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping> {
return this.remoteBridge.getPlaintextBackupsMappingFile(location)
}
persistPlaintextBackupsMappingFile(location: string): Promise<void> {
return this.remoteBridge.persistPlaintextBackupsMappingFile(location)
}
getTextBackupsCount(location: string): Promise<number> {
return this.remoteBridge.getTextBackupsCount(location)
}
async getKeychainValue() {
if (this.useNativeKeychain) {
const keychainValue = await this.remoteBridge.getKeychainValue()
@@ -57,18 +84,10 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
this.remoteBridge.syncComponents(components)
}
onMajorDataChange() {
this.remoteBridge.onMajorDataChange()
}
onSearch(text: string) {
this.remoteBridge.onSearch(text)
}
onInitialDataLoad() {
this.remoteBridge.onInitialDataLoad()
}
async clearAllDataFromDevice(workspaceIdentifiers: string[]): Promise<{ killsApplication: boolean }> {
await super.clearAllDataFromDevice(workspaceIdentifiers)
@@ -77,69 +96,36 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
return { killsApplication: true }
}
async downloadBackup() {
const receiver = window.webClient
receiver.didBeginBackup()
try {
const data = await receiver.requestBackupFile()
if (data) {
this.remoteBridge.saveDataBackup(data)
} else {
receiver.didFinishBackup(false)
}
} catch (error) {
console.error(error)
receiver.didFinishBackup(false)
}
public isLegacyFilesBackupsEnabled(): Promise<boolean> {
return this.remoteBridge.isLegacyFilesBackupsEnabled()
}
async localBackupsCount() {
return this.remoteBridge.localBackupsCount()
public getLegacyFilesBackupsLocation(): Promise<string | undefined> {
return this.remoteBridge.getLegacyFilesBackupsLocation()
}
viewlocalBackups() {
this.remoteBridge.viewlocalBackups()
wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean> {
return this.remoteBridge.wasLegacyTextBackupsExplicitlyDisabled()
}
async deleteLocalBackups() {
return this.remoteBridge.deleteLocalBackups()
getUserDocumentsDirectory(): Promise<string> {
return this.remoteBridge.getUserDocumentsDirectory()
}
public isFilesBackupsEnabled(): Promise<boolean> {
return this.remoteBridge.isFilesBackupsEnabled()
getLegacyTextBackupsLocation(): Promise<string | undefined> {
return this.remoteBridge.getLegacyTextBackupsLocation()
}
public enableFilesBackups(): Promise<void> {
return this.remoteBridge.enableFilesBackups()
saveTextBackupData(workspaceId: string, data: string): Promise<void> {
return this.remoteBridge.saveTextBackupData(workspaceId, data)
}
public disableFilesBackups(): Promise<void> {
return this.remoteBridge.disableFilesBackups()
}
public changeFilesBackupsLocation(): Promise<string | undefined> {
return this.remoteBridge.changeFilesBackupsLocation()
}
public getFilesBackupsLocation(): Promise<string> {
return this.remoteBridge.getFilesBackupsLocation()
}
async getFilesBackupsMappingFile(): Promise<FileBackupsMapping> {
return this.remoteBridge.getFilesBackupsMappingFile()
}
async openFilesBackupsLocation(): Promise<void> {
return this.remoteBridge.openFilesBackupsLocation()
}
openFileBackup(record: FileBackupRecord): Promise<void> {
return this.remoteBridge.openFileBackup(record)
savePlaintextNoteBackup(location: string, uuid: string, name: string, tags: string[], data: string): Promise<void> {
return this.remoteBridge.savePlaintextNoteBackup(location, uuid, name, tags, data)
}
async saveFilesBackupsFile(
location: string,
uuid: string,
metaFile: string,
downloadRequest: {
@@ -148,17 +134,25 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
url: string
},
): Promise<'success' | 'failed'> {
return this.remoteBridge.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
return this.remoteBridge.saveFilesBackupsFile(location, uuid, metaFile, downloadRequest)
}
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
return this.remoteBridge.getFileBackupReadToken(record)
getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken> {
return this.remoteBridge.getFileBackupReadToken(filePath)
}
migrateLegacyFileBackupsToNewStructure(newPath: string): Promise<void> {
return this.remoteBridge.migrateLegacyFileBackupsToNewStructure(newPath)
}
readNextChunk(token: string): Promise<FileBackupReadChunkResponse> {
return this.remoteBridge.readNextChunk(token)
}
monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void> {
return this.remoteBridge.monitorPlaintextBackupsLocationForChanges(backupsDirectory)
}
async performHardReset(): Promise<void> {
console.error('performHardReset is not yet implemented')
}

View File

@@ -1,28 +1,25 @@
import { IpcRendererEvent } from 'electron/renderer'
import { MessageToWebApp } from '../Shared/IpcMessages'
import { ElectronMainEvents, MainEventHandler } from '../Shared/ElectronMainEvents'
const { ipcRenderer } = require('electron')
const RemoteBridge = require('@electron/remote').getGlobal('RemoteBridge')
const { contextBridge } = require('electron')
type MainEventCallback = (event: IpcRendererEvent, value: any) => void
process.once('loaded', function () {
contextBridge.exposeInMainWorld('electronRemoteBridge', RemoteBridge.exposableValue)
contextBridge.exposeInMainWorld('electronMainEvents', {
handleUpdateAvailable: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.UpdateAvailable, callback),
const mainEvents: ElectronMainEvents = {
setUpdateAvailableHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.UpdateAvailable, handler),
handlePerformAutomatedBackup: (callback: MainEventCallback) =>
ipcRenderer.on(MessageToWebApp.PerformAutomatedBackup, callback),
setWindowBlurredHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.WindowBlurred, handler),
handleFinishedSavingBackup: (callback: MainEventCallback) =>
ipcRenderer.on(MessageToWebApp.FinishedSavingBackup, callback),
setWindowFocusedHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.WindowFocused, handler),
handleWindowBlurred: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowBlurred, callback),
setWatchedDirectoriesChangeHandler: (handler: MainEventHandler) =>
ipcRenderer.on(MessageToWebApp.WatchedDirectoriesChanges, handler),
handleWindowFocused: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowFocused, callback),
setInstallComponentCompleteHandler: (handler: MainEventHandler) =>
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, handler),
}
handleInstallComponentComplete: (callback: MainEventCallback) =>
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, callback),
})
contextBridge.exposeInMainWorld('electronMainEvents', mainEvents)
})

View File

@@ -1,8 +1,12 @@
import { DesktopClientRequiresWebMethods } from '@web/Application/Device/DesktopSnjsExports'
import {
DesktopClientRequiresWebMethods,
DesktopWatchedDirectoriesChanges,
} from '@web/Application/Device/DesktopSnjsExports'
import { StartApplication } from '@web/Application/Device/StartApplication'
import { IpcRendererEvent } from 'electron/renderer'
import { CrossProcessBridge } from './CrossProcessBridge'
import { DesktopDevice } from './DesktopDevice'
import { ElectronMainEvents } from '../Shared/ElectronMainEvents'
declare const DEFAULT_SYNC_SERVER: string
declare const WEBSOCKET_URL: string
@@ -23,7 +27,7 @@ declare global {
purchaseUrl: string
startApplication: StartApplication
zip: unknown
electronMainEvents: any
electronMainEvents: ElectronMainEvents
}
}
@@ -128,26 +132,22 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
}
}
window.electronMainEvents.handleUpdateAvailable(() => {
window.electronMainEvents.setUpdateAvailableHandler(() => {
window.webClient.updateAvailable()
})
window.electronMainEvents.handlePerformAutomatedBackup(() => {
void window.device.downloadBackup()
})
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: { success: boolean }) => {
window.webClient.didFinishBackup(data.success)
})
window.electronMainEvents.handleWindowBlurred(() => {
window.electronMainEvents.setWindowBlurredHandler(() => {
window.webClient.windowLostFocus()
})
window.electronMainEvents.handleWindowFocused(() => {
window.electronMainEvents.setWindowFocusedHandler(() => {
window.webClient.windowGainedFocus()
})
window.electronMainEvents.handleInstallComponentComplete((_: IpcRendererEvent, data: any) => {
window.electronMainEvents.setInstallComponentCompleteHandler((_: IpcRendererEvent, data: any) => {
void window.webClient.onComponentInstallationComplete(data.component, undefined)
})
window.electronMainEvents.setWatchedDirectoriesChangeHandler((_: IpcRendererEvent, changes: unknown) => {
void window.webClient.handleWatchedDirectoriesChanges(changes as DesktopWatchedDirectoriesChanges)
})