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

@@ -17,12 +17,12 @@ import {
WebApplicationInterface,
MobileDeviceInterface,
MobileUnlockTiming,
InternalEventBus,
DecryptedItem,
EditorIdentifier,
FeatureIdentifier,
Environment,
ApplicationOptionsDefaults,
BackupServiceInterface,
} from '@standardnotes/snjs'
import { makeObservable, observable } from 'mobx'
import { startAuthentication, startRegistration } from '@simplewebauthn/browser'
@@ -93,27 +93,26 @@ export class WebApplication extends SNApplication implements WebApplicationInter
})
deviceInterface.setApplication(this)
const internalEventBus = new InternalEventBus()
this.itemControllerGroup = new ItemGroupController(this)
this.routeService = new RouteService(this, internalEventBus)
this.routeService = new RouteService(this, this.internalEventBus)
this.webServices = {} as WebServices
this.webServices.keyboardService = new KeyboardService(platform, this.environment)
this.webServices.archiveService = new ArchiveManager(this)
this.webServices.themeService = new ThemeManager(this, internalEventBus)
this.webServices.themeService = new ThemeManager(this, this.internalEventBus)
this.webServices.autolockService = this.isNativeMobileWeb()
? undefined
: new AutolockService(this, internalEventBus)
: new AutolockService(this, this.internalEventBus)
this.webServices.desktopService = isDesktopDevice(deviceInterface)
? new DesktopManager(this, deviceInterface)
? new DesktopManager(this, deviceInterface, this.fileBackups as BackupServiceInterface)
: undefined
this.webServices.viewControllerManager = new ViewControllerManager(this, deviceInterface)
this.webServices.changelogService = new ChangelogService(this.environment, this.storage)
this.webServices.momentsService = new MomentsService(
this,
this.webServices.viewControllerManager.filesController,
internalEventBus,
this.internalEventBus,
)
if (this.isNativeMobileWeb()) {
@@ -181,6 +180,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter
for (const observer of this.webEventObservers) {
observer(event, data)
}
this.internalEventBus.publish({ type: event, payload: data })
}
publishPanelDidResizeEvent(name: string, width: number, collapsed: boolean) {
@@ -268,16 +269,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter
return this.protocolUpgradeAvailable()
}
downloadBackup(): void | Promise<void> {
if (isDesktopDevice(this.deviceInterface)) {
return this.deviceInterface.downloadBackup()
}
}
async signOutAndDeleteLocalBackups(): Promise<void> {
isDesktopDevice(this.deviceInterface) && (await this.deviceInterface.deleteLocalBackups())
return this.user.signOut()
performDesktopTextBackup(): void | Promise<void> {
return this.getDesktopService()?.saveDesktopBackup()
}
isGlobalSpellcheckEnabled(): boolean {

View File

@@ -14,6 +14,8 @@ import {
DesktopDeviceInterface,
WebApplicationInterface,
WebAppEvent,
BackupServiceInterface,
DesktopWatchedDirectoriesChanges,
} from '@standardnotes/snjs'
export class DesktopManager
@@ -27,10 +29,34 @@ export class DesktopManager
dataLoaded = false
lastSearchedText?: string
constructor(application: WebApplicationInterface, private device: DesktopDeviceInterface) {
private textBackupsInterval: ReturnType<typeof setInterval> | undefined
private needsInitialTextBackup = false
constructor(
application: WebApplicationInterface,
private device: DesktopDeviceInterface,
private backups: BackupServiceInterface,
) {
super(application, new InternalEventBus())
}
async handleWatchedDirectoriesChanges(changes: DesktopWatchedDirectoriesChanges): Promise<void> {
void this.backups.importWatchedDirectoryChanges(changes)
}
beginTextBackupsTimer() {
if (this.textBackupsInterval) {
clearInterval(this.textBackupsInterval)
}
this.needsInitialTextBackup = true
const hoursInterval = 12
const seconds = hoursInterval * 60 * 60
const milliseconds = seconds * 1000
this.textBackupsInterval = setInterval(this.saveDesktopBackup, milliseconds)
}
get webApplication() {
return this.application as WebApplicationInterface
}
@@ -44,14 +70,35 @@ export class DesktopManager
super.onAppEvent(eventName).catch(console.error)
if (eventName === ApplicationEvent.LocalDataLoaded) {
this.dataLoaded = true
this.device.onInitialDataLoad()
if (this.backups.isTextBackupsEnabled()) {
this.beginTextBackupsTimer()
}
} else if (eventName === ApplicationEvent.MajorDataChange) {
this.device.onMajorDataChange()
void this.saveDesktopBackup()
}
}
saveBackup() {
this.device.onMajorDataChange()
async saveDesktopBackup() {
this.webApplication.notifyWebEvent(WebAppEvent.BeganBackupDownload)
const data = await this.getBackupFile()
if (data) {
await this.webApplication.fileBackups?.saveTextBackupData(data)
this.webApplication.notifyWebEvent(WebAppEvent.EndedBackupDownload, { success: true })
}
}
private async getBackupFile(): Promise<string | undefined> {
const encrypted = this.application.hasProtectionSources()
const data = encrypted
? await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
: await this.application.createDecryptedBackupFile()
if (data) {
return JSON.stringify(data, null, 2)
}
return undefined
}
getExtServerHost(): string {
@@ -111,6 +158,11 @@ export class DesktopManager
windowLostFocus(): void {
this.webApplication.notifyWebEvent(WebAppEvent.WindowDidBlur)
if (this.needsInitialTextBackup) {
this.needsInitialTextBackup = false
void this.saveDesktopBackup()
}
}
async onComponentInstallationComplete(componentData: DecryptedTransferPayload<ComponentContent>) {
@@ -136,25 +188,4 @@ export class DesktopManager
observer.callback(updatedComponent as SNComponent)
}
}
async requestBackupFile(): Promise<string | undefined> {
const encrypted = this.application.hasProtectionSources()
const data = encrypted
? await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
: await this.application.createDecryptedBackupFile()
if (data) {
return JSON.stringify(data, null, 2)
}
return undefined
}
didBeginBackup() {
this.webApplication.notifyWebEvent(WebAppEvent.BeganBackupDownload)
}
didFinishBackup(success: boolean) {
this.webApplication.notifyWebEvent(WebAppEvent.EndedBackupDownload, { success })
}
}

View File

@@ -10,4 +10,7 @@ export {
FileBackupReadToken,
FileBackupReadChunkResponse,
FileDownloadProgress,
PlaintextBackupsMapping,
DesktopWatchedDirectoriesChanges,
DesktopWatchedDirectoriesChange,
} from '@standardnotes/snjs'