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

@@ -188,7 +188,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private declare _getRevision: GetRevision
private declare _deleteRevision: DeleteRevision
private internalEventBus!: ExternalServices.InternalEventBusInterface
public internalEventBus!: ExternalServices.InternalEventBusInterface
private eventHandlers: ApplicationObserver[] = []
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1184,13 +1184,13 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.createSettingsService()
this.createFeaturesService()
this.createComponentManager()
this.createMigrationService()
this.createMfaService()
this.createStatusService()
if (isDesktopDevice(this.deviceInterface)) {
this.createFilesBackupService(this.deviceInterface)
}
this.createMigrationService()
this.createFileService()
this.createIntegrityService()
@@ -1381,6 +1381,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
identifier: this.identifier,
internalEventBus: this.internalEventBus,
legacySessionStorageMapper: this.legacySessionStorageMapper,
backups: this.fileBackups,
})
this.services.push(this.migrationService)
}
@@ -1584,6 +1585,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.httpService,
this.sessionStorageMapper,
this.legacySessionStorageMapper,
this.identifier,
this.internalEventBus,
)
this.serviceObservers.push(
@@ -1761,6 +1763,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
device,
this.statusService,
this.options.crypto,
this.storage,
this.sessions,
this.payloadManager,
this.historyManager,
this.internalEventBus,
)
this.services.push(this.filesBackupService)

View File

@@ -1,6 +1,6 @@
import { BackupServiceInterface } from '@standardnotes/files'
import { Environment } from '@standardnotes/models'
import { DeviceInterface, InternalEventBusInterface, EncryptionService } from '@standardnotes/services'
import { SNSessionManager } from '../Services/Session/SessionManager'
import { ApplicationIdentifier } from '@standardnotes/common'
import { ItemManager } from '@Lib/Services/Items/ItemManager'
@@ -13,6 +13,7 @@ export type MigrationServices = {
storageService: DiskStorageService
challengeService: ChallengeService
sessionManager: SNSessionManager
backups?: BackupServiceInterface
itemManager: ItemManager
singletonManager: SNSingletonManager
featuresService: SNFeaturesService

View File

@@ -0,0 +1,51 @@
import {
ApplicationStage,
FileBackupsDirectoryName,
StorageKey,
TextBackupsDirectoryName,
isDesktopDevice,
} from '@standardnotes/services'
import { Migration } from '@Lib/Migrations/Migration'
export class Migration2_167_6 extends Migration {
static override version(): string {
return '2.167.6'
}
protected registerStageHandlers(): void {
this.registerStageHandler(ApplicationStage.Launched_10, async () => {
await this.migrateStorageKeysForDesktopBackups()
this.markDone()
})
}
private async migrateStorageKeysForDesktopBackups(): Promise<void> {
const device = this.services.deviceInterface
if (!isDesktopDevice(device) || !this.services.backups) {
return
}
const fileBackupsEnabled = await device.isLegacyFilesBackupsEnabled()
this.services.storageService.setValue(StorageKey.FileBackupsEnabled, fileBackupsEnabled)
if (fileBackupsEnabled) {
const legacyLocation = await device.getLegacyFilesBackupsLocation()
const newLocation = `${legacyLocation}/${this.services.backups.prependWorkspacePathForPath(
FileBackupsDirectoryName,
)}`
await device.migrateLegacyFileBackupsToNewStructure(newLocation)
this.services.storageService.setValue(StorageKey.FileBackupsLocation, newLocation)
}
const wasLegacyDisabled = await device.wasLegacyTextBackupsExplicitlyDisabled()
if (wasLegacyDisabled) {
this.services.storageService.setValue(StorageKey.TextBackupsEnabled, false)
} else {
const newTextBackupsLocation = `${await device.getLegacyTextBackupsLocation()}/${this.services.backups.prependWorkspacePathForPath(
TextBackupsDirectoryName,
)}`
this.services.storageService.setValue(StorageKey.TextBackupsLocation, newTextBackupsLocation)
this.services.storageService.setValue(StorageKey.TextBackupsEnabled, true)
}
}
}

View File

@@ -0,0 +1,5 @@
## To create a migration:
1. Create a new file inside versions specifiying the would-be version of SNJS that would result when publishing your migration. For example, if the current SNJS version is 1.0.0 in package.json, your migration version should be 1.0.1 to target users below this version.
2. **Important** Export your migration inside the index.ts file.

View File

@@ -3,7 +3,15 @@ import { Migration2_7_0 } from './2_7_0'
import { Migration2_20_0 } from './2_20_0'
import { Migration2_36_0 } from './2_36_0'
import { Migration2_42_0 } from './2_42_0'
import { Migration2_167_6 } from './2_167_6'
export const MigrationClasses = [Migration2_0_15, Migration2_7_0, Migration2_20_0, Migration2_36_0, Migration2_42_0]
export const MigrationClasses = [
Migration2_0_15,
Migration2_7_0,
Migration2_20_0,
Migration2_36_0,
Migration2_42_0,
Migration2_167_6,
]
export { Migration2_0_15, Migration2_7_0, Migration2_20_0, Migration2_36_0, Migration2_42_0 }
export { Migration2_0_15, Migration2_7_0, Migration2_20_0, Migration2_36_0, Migration2_42_0, Migration2_167_6 }

View File

@@ -5,7 +5,12 @@ import { DiskStorageService } from '@Lib/Services/Storage/DiskStorageService'
import { UuidString } from '../../Types/UuidString'
import * as Models from '@standardnotes/models'
import { SNNote } from '@standardnotes/models'
import { AbstractService, DeviceInterface, InternalEventBusInterface } from '@standardnotes/services'
import {
AbstractService,
DeviceInterface,
HistoryServiceInterface,
InternalEventBusInterface,
} from '@standardnotes/services'
/** The amount of revisions per item above which should call for an optimization. */
const DefaultItemRevisionsThreshold = 20
@@ -25,7 +30,7 @@ const LargeEntryDeltaThreshold = 25
* 2. Remote server history. Entries are automatically added by the server and must be
* retrieved per item via an API call.
*/
export class SNHistoryManager extends AbstractService {
export class SNHistoryManager extends AbstractService implements HistoryServiceInterface {
private removeChangeObserver: () => void
/**

View File

@@ -343,13 +343,13 @@ export class ItemManager
/**
* Returns all items that an item directly references
*/
public referencesForItem(
public referencesForItem<I extends Models.DecryptedItemInterface = Models.DecryptedItemInterface>(
itemToLookupUuidFor: Models.DecryptedItemInterface,
contentType?: ContentType,
): Models.DecryptedItemInterface[] {
const item = this.findSureItem(itemToLookupUuidFor.uuid)
): I[] {
const item = this.findSureItem<I>(itemToLookupUuidFor.uuid)
const uuids = item.references.map((ref) => ref.uuid)
let references = this.findItems(uuids)
let references = this.findItems<I>(uuids)
if (contentType) {
references = references.filter((ref) => {
return ref?.content_type === contentType

View File

@@ -54,10 +54,7 @@ export class SNMigrationService extends AbstractService {
await this.markMigrationsAsDone()
})
} else {
await this.services.deviceInterface.setRawStorageValue(
namespacedKey(this.services.identifier, RawStorageKey.SnjsVersion),
SnjsVersion,
)
await this.markMigrationsAsDone()
}
}

View File

@@ -101,6 +101,7 @@ export class SNSessionManager
private httpService: HttpServiceInterface,
private sessionStorageMapper: MapperInterface<Session, Record<string, unknown>>,
private legacySessionStorageMapper: MapperInterface<LegacySession, Record<string, unknown>>,
private workspaceIdentifier: string,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -130,6 +131,14 @@ export class SNSessionManager
super.deinit()
}
public getWorkspaceDisplayIdentifier(): string {
if (this.user) {
return this.user.email
} else {
return this.workspaceIdentifier
}
}
private setUser(user?: User) {
this.user = user
this.apiService.setUser(user)

View File

@@ -3,7 +3,7 @@ chai.use(chaiAsPromised)
const expect = chai.expect
describe('migrations', () => {
const allMigrations = ['2.0.15', '2.7.0', '2.20.0', '2.36.0', '2.42.0']
const allMigrations = ['2.0.15', '2.7.0', '2.20.0', '2.36.0', '2.42.0', '2.167.6']
beforeEach(async () => {
localStorage.clear()

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.167.5",
"version": "2.167.6",
"engines": {
"node": ">=16.0.0 <17.0.0"
},