From 69b2af7612bdc330665490dd4e114ce8a3bf25ae Mon Sep 17 00:00:00 2001 From: StandardNotes CI Date: Wed, 4 Jan 2023 19:10:43 +0000 Subject: [PATCH 1/2] chore(release): publish - @standardnotes/desktop@3.104.67 - @standardnotes/mobile@3.50.3 - @standardnotes/releases@1.4.90 - @standardnotes/toast@1.3.16 - @standardnotes/ui-services@1.22.3 - @standardnotes/web@3.132.3 --- packages/desktop/CHANGELOG.md | 4 ++++ packages/desktop/package.json | 2 +- packages/mobile/CHANGELOG.md | 4 ++++ packages/mobile/package.json | 2 +- packages/releases/CHANGELOG.md | 4 ++++ packages/releases/package.json | 2 +- packages/toast/CHANGELOG.md | 6 ++++++ packages/toast/package.json | 2 +- packages/ui-services/CHANGELOG.md | 4 ++++ packages/ui-services/package.json | 2 +- packages/web/CHANGELOG.md | 4 ++++ packages/web/CHANGELOG.md.json | 11 +++++++++++ packages/web/package.json | 2 +- 13 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md index c4b44ad71..4e295b6b1 100644 --- a/packages/desktop/CHANGELOG.md +++ b/packages/desktop/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.104.67](https://github.com/standardnotes/app/compare/@standardnotes/desktop@3.132.2...@standardnotes/desktop@3.104.67) (2023-01-04) + +**Note:** Version bump only for package @standardnotes/desktop + ## [3.104.66](https://github.com/standardnotes/app/compare/@standardnotes/desktop@3.132.0...@standardnotes/desktop@3.104.66) (2023-01-04) **Note:** Version bump only for package @standardnotes/desktop diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 9feb42d8d..639fb889e 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@standardnotes/desktop", "main": "./app/dist/index.js", - "version": "3.104.66", + "version": "3.104.67", "license": "AGPL-3.0-or-later", "author": "Standard Notes.", "private": true, diff --git a/packages/mobile/CHANGELOG.md b/packages/mobile/CHANGELOG.md index 24a7b620f..fde20d5d5 100644 --- a/packages/mobile/CHANGELOG.md +++ b/packages/mobile/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.50.3](https://github.com/standardnotes/app/compare/@standardnotes/mobile@3.50.2...@standardnotes/mobile@3.50.3) (2023-01-04) + +**Note:** Version bump only for package @standardnotes/mobile + ## [3.50.2](https://github.com/standardnotes/app/compare/@standardnotes/mobile@3.50.1...@standardnotes/mobile@3.50.2) (2023-01-04) **Note:** Version bump only for package @standardnotes/mobile diff --git a/packages/mobile/package.json b/packages/mobile/package.json index d5c6cccba..7ed5561f7 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@standardnotes/mobile", - "version": "3.50.2", + "version": "3.50.3", "author": "Standard Notes.", "private": true, "license": "AGPL-3.0-or-later", diff --git a/packages/releases/CHANGELOG.md b/packages/releases/CHANGELOG.md index 13da91f5d..b0eec209d 100644 --- a/packages/releases/CHANGELOG.md +++ b/packages/releases/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.4.90](https://github.com/standardnotes/app/compare/@standardnotes/releases@1.4.89...@standardnotes/releases@1.4.90) (2023-01-04) + +**Note:** Version bump only for package @standardnotes/releases + ## [1.4.89](https://github.com/standardnotes/app/compare/@standardnotes/releases@1.4.88...@standardnotes/releases@1.4.89) (2023-01-04) **Note:** Version bump only for package @standardnotes/releases diff --git a/packages/releases/package.json b/packages/releases/package.json index 080348f20..513c452bf 100644 --- a/packages/releases/package.json +++ b/packages/releases/package.json @@ -1,6 +1,6 @@ { "name": "@standardnotes/releases", - "version": "1.4.89", + "version": "1.4.90", "license": "AGPL-3.0-or-later", "main": "dist/releases.json", "types": "dist/index.d.ts", diff --git a/packages/toast/CHANGELOG.md b/packages/toast/CHANGELOG.md index b4e33c0ea..ac54b2eb1 100644 --- a/packages/toast/CHANGELOG.md +++ b/packages/toast/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.3.16](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.15...@standardnotes/toast@1.3.16) (2023-01-04) + +### Bug Fixes + +* Fixed issue where file upload toast would close before completion if you switched windows ([30dda73](https://github.com/standardnotes/app/commit/30dda73e9078bbccf91c3f43307250c84a17e8bd)) + ## [1.3.15](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.14...@standardnotes/toast@1.3.15) (2022-12-29) **Note:** Version bump only for package @standardnotes/toast diff --git a/packages/toast/package.json b/packages/toast/package.json index 512a5c818..1755acee7 100644 --- a/packages/toast/package.json +++ b/packages/toast/package.json @@ -1,6 +1,6 @@ { "name": "@standardnotes/toast", - "version": "1.3.15", + "version": "1.3.16", "private": true, "main": "./src/index.ts", "scripts": { diff --git a/packages/ui-services/CHANGELOG.md b/packages/ui-services/CHANGELOG.md index 45b1c6512..2f0ab122b 100644 --- a/packages/ui-services/CHANGELOG.md +++ b/packages/ui-services/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.22.3](https://github.com/standardnotes/app/compare/@standardnotes/ui-services@1.22.2...@standardnotes/ui-services@1.22.3) (2023-01-04) + +**Note:** Version bump only for package @standardnotes/ui-services + ## [1.22.2](https://github.com/standardnotes/app/compare/@standardnotes/ui-services@1.22.1...@standardnotes/ui-services@1.22.2) (2023-01-03) **Note:** Version bump only for package @standardnotes/ui-services diff --git a/packages/ui-services/package.json b/packages/ui-services/package.json index 1fc0e1305..a71a6321b 100644 --- a/packages/ui-services/package.json +++ b/packages/ui-services/package.json @@ -1,6 +1,6 @@ { "name": "@standardnotes/ui-services", - "version": "1.22.2", + "version": "1.22.3", "engines": { "node": ">=16.0.0 <17.0.0" }, diff --git a/packages/web/CHANGELOG.md b/packages/web/CHANGELOG.md index fb69fed06..5d13922bc 100644 --- a/packages/web/CHANGELOG.md +++ b/packages/web/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.132.3](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.2...@standardnotes/web@3.132.3) (2023-01-04) + +**Note:** Version bump only for package @standardnotes/web + ## [3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.0...@standardnotes/web@3.132.2) (2023-01-04) **Note:** Version bump only for package @standardnotes/web diff --git a/packages/web/CHANGELOG.md.json b/packages/web/CHANGELOG.md.json index 12659207c..b08a6a194 100644 --- a/packages/web/CHANGELOG.md.json +++ b/packages/web/CHANGELOG.md.json @@ -1,5 +1,16 @@ { "versions": [ + { + "version": "3.132.3", + "title": "[3.132.3](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.2...@standardnotes/web@3.132.3) (2023-01-04)", + "date": null, + "body": "**Note:** Version bump only for package @standardnotes/web", + "parsed": { + "_": [ + "Note: Version bump only for package @standardnotes/web" + ] + } + }, { "version": "3.132.2", "title": "[3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/web@3.132.0...@standardnotes/web@3.132.2) (2023-01-04)", diff --git a/packages/web/package.json b/packages/web/package.json index 760245e66..161b0e3a4 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@standardnotes/web", - "version": "3.132.2", + "version": "3.132.3", "license": "AGPL-3.0-or-later", "main": "dist/app.js", "author": "Standard Notes.", From 59fc68296b227b8624a5326d60d51da7d0953171 Mon Sep 17 00:00:00 2001 From: Mo Date: Wed, 4 Jan 2023 13:31:45 -0600 Subject: [PATCH 2/2] refactor: optimize delay between batches on mobile to allow UI interactivity during load (#2129) --- package.json | 2 +- .../services/src/Domain/Strings/Messages.ts | 3 +- .../src/Domain/User/UserClientInterface.ts | 2 ++ .../services/src/Domain/User/UserService.ts | 6 ++++ packages/snjs/README.md | 2 +- packages/snjs/lib/Application/Application.ts | 1 + .../snjs/lib/Application/Options/Defaults.ts | 8 ++++- .../Application/Options/OptionalOptions.ts | 8 +++-- packages/snjs/lib/Migrations/Base.ts | 8 ++--- .../snjs/lib/Services/Sync/SyncService.ts | 12 +++++-- packages/snjs/mocha/application.test.js | 21 ++++++------ packages/snjs/mocha/auth.test.js | 6 ++-- packages/snjs/mocha/lib/AppContext.js | 32 +++++++++++++++++++ .../snjs/mocha/model_tests/appmodels.test.js | 4 ++- .../snjs/mocha/model_tests/importing.test.js | 10 ++++-- .../snjs/mocha/model_tests/mapping.test.js | 4 ++- .../snjs/mocha/model_tests/notes_tags.test.js | 4 ++- .../model_tests/notes_tags_folders.test.js | 4 ++- packages/snjs/mocha/preferences.test.js | 6 +++- packages/snjs/mocha/singletons.test.js | 6 +++- packages/snjs/mocha/storage.test.js | 7 ++-- .../snjs/mocha/sync_tests/offline.test.js | 4 ++- packages/snjs/mocha/sync_tests/online.test.js | 2 +- packages/snjs/mocha/upgrading.test.js | 8 +++-- packages/utils/src/Domain/Utils/Utils.ts | 4 +-- .../javascripts/Application/Application.ts | 6 +++- .../AccountMenu/GeneralAccountMenu.tsx | 20 ------------ .../ApplicationView/ApplicationView.tsx | 2 +- .../ItemList/ItemListController.spec.ts | 10 +++++- .../ItemList/ItemListController.ts | 4 +++ .../Navigation/NavigationController.ts | 5 ++- .../Controllers/SelectedItemsController.ts | 17 +++++++--- 32 files changed, 171 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index 3093a55cc..27600982b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "build:services": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/services run build", "build:api": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/api run build", "start:server:web": "lerna run start --scope=@standardnotes/web", - "start:server:e2e": "lerna run start:test-server --scope=@standardnotes/snjs", + "e2e": "lerna run start:test-server --scope=@standardnotes/snjs", "reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install", "release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"", "publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose", diff --git a/packages/services/src/Domain/Strings/Messages.ts b/packages/services/src/Domain/Strings/Messages.ts index e7ba268d3..eab4d5924 100644 --- a/packages/services/src/Domain/Strings/Messages.ts +++ b/packages/services/src/Domain/Strings/Messages.ts @@ -182,5 +182,6 @@ export const ErrorAlertStrings = { export const KeychainRecoveryStrings = { Title: 'Restore Keychain', - Text: "We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password to restore your account keys.", + Text: (email: string) => + `We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password for "${email}" to restore your account keys.`, } diff --git a/packages/services/src/Domain/User/UserClientInterface.ts b/packages/services/src/Domain/User/UserClientInterface.ts index 822b74dc5..6c4404986 100644 --- a/packages/services/src/Domain/User/UserClientInterface.ts +++ b/packages/services/src/Domain/User/UserClientInterface.ts @@ -1,3 +1,4 @@ +import { Base64String } from '@standardnotes/sncrypto-common' import { UserRequestType } from '@standardnotes/common' import { DeinitSource } from '../Application/DeinitSource' @@ -8,4 +9,5 @@ export interface UserClientInterface { }> signOut(force?: boolean, source?: DeinitSource): Promise submitUserRequest(requestType: UserRequestType): Promise + populateSessionFromDemoShareToken(token: Base64String): Promise } diff --git a/packages/services/src/Domain/User/UserService.ts b/packages/services/src/Domain/User/UserService.ts index 9fee66740..77cf19c39 100644 --- a/packages/services/src/Domain/User/UserService.ts +++ b/packages/services/src/Domain/User/UserService.ts @@ -1,3 +1,4 @@ +import { Base64String } from '@standardnotes/sncrypto-common' import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption' import { HttpResponse, SignInResponse, User } from '@standardnotes/responses' import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common' @@ -470,6 +471,11 @@ export class UserService extends AbstractService } } + public async populateSessionFromDemoShareToken(token: Base64String): Promise { + await this.sessionManager.populateSessionFromDemoShareToken(token) + await this.notifyEvent(AccountEvent.SignedInOrRegistered) + } + private async setPasscodeWithoutWarning(passcode: string, origination: KeyParamsOrigination) { const identifier = UuidGenerator.GenerateUuid() const key = await this.protocolService.createRootKey(identifier, passcode, origination) diff --git a/packages/snjs/README.md b/packages/snjs/README.md index 2a1c5ca1f..d75f04711 100644 --- a/packages/snjs/README.md +++ b/packages/snjs/README.md @@ -53,7 +53,7 @@ Once the server infrastructure is ready, and you've built all packages, you can In app repo: ``` -yarn start:server:e2e +yarn e2e ``` Once you are finished you can close the running local server on E2E repo by typing: diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 903b74b05..1136798ed 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -1581,6 +1581,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli this.identifier, { loadBatchSize: this.options.loadBatchSize, + sleepBetweenBatches: this.options.sleepBetweenBatches, }, this.internalEventBus, ) diff --git a/packages/snjs/lib/Application/Options/Defaults.ts b/packages/snjs/lib/Application/Options/Defaults.ts index 56e302d0b..79bd2ba2f 100644 --- a/packages/snjs/lib/Application/Options/Defaults.ts +++ b/packages/snjs/lib/Application/Options/Defaults.ts @@ -1,9 +1,15 @@ -import { ApplicationSyncOptions } from './OptionalOptions' +import { ApplicationDisplayOptions, ApplicationSyncOptions } from './OptionalOptions' export interface ApplicationOptionsWhichHaveDefaults { loadBatchSize: ApplicationSyncOptions['loadBatchSize'] + sleepBetweenBatches: ApplicationSyncOptions['sleepBetweenBatches'] + allowNoteSelectionStatePersistence: ApplicationDisplayOptions['allowNoteSelectionStatePersistence'] + allowMultipleSelection: ApplicationDisplayOptions['allowMultipleSelection'] } export const ApplicationOptionsDefaults: ApplicationOptionsWhichHaveDefaults = { loadBatchSize: 700, + sleepBetweenBatches: 10, + allowMultipleSelection: true, + allowNoteSelectionStatePersistence: true, } diff --git a/packages/snjs/lib/Application/Options/OptionalOptions.ts b/packages/snjs/lib/Application/Options/OptionalOptions.ts index 19eadd6b9..33e51a15b 100644 --- a/packages/snjs/lib/Application/Options/OptionalOptions.ts +++ b/packages/snjs/lib/Application/Options/OptionalOptions.ts @@ -3,10 +3,14 @@ export interface ApplicationSyncOptions { * The size of the item batch to decrypt and render upon application load. */ loadBatchSize: number + + sleepBetweenBatches: number } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ApplicationDisplayOptions {} +export interface ApplicationDisplayOptions { + allowNoteSelectionStatePersistence: boolean + allowMultipleSelection: boolean +} export interface ApplicationOptionalConfiguratioOptions { /** diff --git a/packages/snjs/lib/Migrations/Base.ts b/packages/snjs/lib/Migrations/Base.ts index 4216f2a5e..210fc2694 100644 --- a/packages/snjs/lib/Migrations/Base.ts +++ b/packages/snjs/lib/Migrations/Base.ts @@ -1,4 +1,4 @@ -import { AnyKeyParamsContent } from '@standardnotes/common' +import { AnyKeyParamsContent, KeyParamsContent004 } from '@standardnotes/common' import { EncryptedPayload, EncryptedTransferPayload, isErrorDecryptingPayload } from '@standardnotes/models' import { PreviousSnjsVersion1_0_0, PreviousSnjsVersion2_0_0, SnjsVersion } from '../Version' import { Migration } from '@Lib/Migrations/Migration' @@ -165,7 +165,7 @@ export class BaseMigration extends Migration { } private async repairMissingKeychain() { - const rawAccountParams = await this.reader.getAccountKeyParams() + const rawAccountParams = (await this.reader.getAccountKeyParams()) as AnyKeyParamsContent /** Choose an item to decrypt against */ const allItems = ( @@ -196,14 +196,14 @@ export class BaseMigration extends Migration { ChallengeReason.Custom, false, KeychainRecoveryStrings.Title, - KeychainRecoveryStrings.Text, + KeychainRecoveryStrings.Text((rawAccountParams as KeyParamsContent004).identifier), ) return new Promise((resolve) => { this.services.challengeService.addChallengeObserver(challenge, { onNonvalidatedSubmit: async (challengeResponse) => { const password = challengeResponse.values[0].value as string - const accountParams = this.services.protocolService.createKeyParams(rawAccountParams as AnyKeyParamsContent) + const accountParams = this.services.protocolService.createKeyParams(rawAccountParams) const rootKey = await this.services.protocolService.computeRootKey(password, accountParams) /** TS can't detect we returned early above if itemToDecrypt is null */ diff --git a/packages/snjs/lib/Services/Sync/SyncService.ts b/packages/snjs/lib/Services/Sync/SyncService.ts index 60e45f782..ddcce3fac 100644 --- a/packages/snjs/lib/Services/Sync/SyncService.ts +++ b/packages/snjs/lib/Services/Sync/SyncService.ts @@ -298,6 +298,9 @@ export class SNSyncService ? chunks.fullEntries.remainingChunks : chunks.keys.remainingChunks + let chunkIndex = 0 + const ChunkIndexOfContentTypePriorityItems = 0 + for (const chunk of remainingChunks) { const dbEntries = isChunkFullEntry(chunk) ? chunk.entries @@ -314,7 +317,14 @@ export class SNSyncService .filter(isNotUndefined) await this.processPayloadBatch(payloads, totalProcessedCount, payloadCount) + + const shouldSleepOnlyAfterFirstRegularBatch = chunkIndex > ChunkIndexOfContentTypePriorityItems + if (shouldSleepOnlyAfterFirstRegularBatch) { + await sleep(this.options.sleepBetweenBatches, false, 'Sleeping to allow interface to update') + } + totalProcessedCount += payloads.length + chunkIndex++ } this.databaseLoaded = true @@ -353,8 +363,6 @@ export class SNSyncService if (currentPosition != undefined && payloadCount != undefined) { this.opStatus.setDatabaseLoadStatus(currentPosition, payloadCount, false) } - - await sleep(1, false) } private setLastSyncToken(token: string) { diff --git a/packages/snjs/mocha/application.test.js b/packages/snjs/mocha/application.test.js index 779a26e97..b3f5af9e5 100644 --- a/packages/snjs/mocha/application.test.js +++ b/packages/snjs/mocha/application.test.js @@ -20,16 +20,19 @@ describe('application instances', () => { }) it('two distinct applications should not share model manager state', async () => { - const app1 = await Factory.createAndInitializeApplication('app1') - const app2 = await Factory.createAndInitializeApplication('app2') - expect(app1.payloadManager).to.equal(app1.payloadManager) - expect(app1.payloadManager).to.not.equal(app2.payloadManager) + const context1 = await Factory.createAppContext({ identifier: 'app1' }) + const context2 = await Factory.createAppContext({ identifier: 'app2' }) + await Promise.all([context1.launch(), context2.launch()]) - await Factory.createMappedNote(app1) - expect(app1.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1) - expect(app2.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems) - await Factory.safeDeinit(app1) - await Factory.safeDeinit(app2) + expect(context1.application.payloadManager).to.equal(context1.application.payloadManager) + expect(context1.application.payloadManager).to.not.equal(context2.application.payloadManager) + + await Factory.createMappedNote(context1.application) + expect(context1.application.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1) + expect(context2.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems) + + await context1.deinit() + await context2.deinit() }) it('two distinct applications should not share storage manager state', async () => { diff --git a/packages/snjs/mocha/auth.test.js b/packages/snjs/mocha/auth.test.js index 8fe960457..c4e0c77c8 100644 --- a/packages/snjs/mocha/auth.test.js +++ b/packages/snjs/mocha/auth.test.js @@ -16,7 +16,9 @@ describe('basic auth', function () { beforeEach(async function () { localStorage.clear() this.expectedItemCount = BaseItemCounts.DefaultItems - this.application = await Factory.createInitAppWithFakeCrypto() + this.context = await Factory.createAppContext() + await this.context.launch() + this.application = this.context.application this.email = UuidGenerator.GenerateUuid() this.password = UuidGenerator.GenerateUuid() }) @@ -402,7 +404,7 @@ describe('basic auth', function () { await this.application.syncService.markAllItemsAsNeedingSyncAndPersist() await this.application.syncService.sync(syncOptions) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + this.application = await this.context.signout() expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems) expect(this.application.payloadManager.invalidPayloads.length).to.equal(0) diff --git a/packages/snjs/mocha/lib/AppContext.js b/packages/snjs/mocha/lib/AppContext.js index 912b21a25..592b1f4dd 100644 --- a/packages/snjs/mocha/lib/AppContext.js +++ b/packages/snjs/mocha/lib/AppContext.js @@ -163,6 +163,13 @@ export class AppContext { return newApplication } + async signout() { + await this.application.user.signOut() + await this.initialize() + await this.launch() + return this.application + } + syncWithIntegrityCheck() { return this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) } @@ -189,11 +196,36 @@ export class AppContext { }) } + awaitUserPrefsSingletonCreation() { + const preferences = this.application.preferencesService.preferences + if (preferences) { + return + } + + let didCompleteRelevantSync = false + return new Promise((resolve) => { + this.application.syncService.addEventObserver((eventName, data) => { + if (!didCompleteRelevantSync) { + if (data?.savedPayloads) { + const matching = data.savedPayloads.find((p) => { + return p.content_type === ContentType.UserPrefs + }) + if (matching) { + didCompleteRelevantSync = true + resolve() + } + } + } + }) + }) + } + async launch({ awaitDatabaseLoad = true, receiveChallenge } = { awaitDatabaseLoad: true }) { await this.application.prepareForLaunch({ receiveChallenge: receiveChallenge || this.handleChallenge, }) await this.application.launch(awaitDatabaseLoad) + await this.awaitUserPrefsSingletonCreation() } async deinit() { diff --git a/packages/snjs/mocha/model_tests/appmodels.test.js b/packages/snjs/mocha/model_tests/appmodels.test.js index 5b6aca03b..13847c349 100644 --- a/packages/snjs/mocha/model_tests/appmodels.test.js +++ b/packages/snjs/mocha/model_tests/appmodels.test.js @@ -21,7 +21,9 @@ describe('app models', () => { beforeEach(async function () { this.expectedItemCount = BaseItemCounts.DefaultItems - this.application = await Factory.createInitAppWithFakeCrypto() + this.context = await Factory.createAppContext() + this.application = this.context.application + await this.context.launch() }) afterEach(async function () { diff --git a/packages/snjs/mocha/model_tests/importing.test.js b/packages/snjs/mocha/model_tests/importing.test.js index 2b9e41f74..f23bc4b26 100644 --- a/packages/snjs/mocha/model_tests/importing.test.js +++ b/packages/snjs/mocha/model_tests/importing.test.js @@ -13,6 +13,7 @@ describe('importing', function () { let application let email let password + let context beforeEach(function () { localStorage.clear() @@ -20,11 +21,16 @@ describe('importing', function () { const setup = async ({ fakeCrypto }) => { expectedItemCount = BaseItemCounts.DefaultItems + if (fakeCrypto) { - application = await Factory.createInitAppWithFakeCrypto() + context = await Factory.createAppContext() } else { - application = await Factory.createInitAppWithRealCrypto() + context = await Factory.createAppContextWithRealCrypto() } + + await context.launch() + application = context.application + email = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid() Factory.handlePasswordChallenges(application, password) diff --git a/packages/snjs/mocha/model_tests/mapping.test.js b/packages/snjs/mocha/model_tests/mapping.test.js index bef0c3d70..791c04adb 100644 --- a/packages/snjs/mocha/model_tests/mapping.test.js +++ b/packages/snjs/mocha/model_tests/mapping.test.js @@ -9,7 +9,9 @@ const expect = chai.expect describe('model manager mapping', () => { beforeEach(async function () { this.expectedItemCount = BaseItemCounts.DefaultItems - this.application = await Factory.createInitAppWithFakeCrypto() + this.context = await Factory.createAppContext() + await this.context.launch() + this.application = this.context.application }) afterEach(async function () { diff --git a/packages/snjs/mocha/model_tests/notes_tags.test.js b/packages/snjs/mocha/model_tests/notes_tags.test.js index e057d81ce..2ebe3d37a 100644 --- a/packages/snjs/mocha/model_tests/notes_tags.test.js +++ b/packages/snjs/mocha/model_tests/notes_tags.test.js @@ -14,7 +14,9 @@ describe('notes and tags', () => { beforeEach(async function () { this.expectedItemCount = BaseItemCounts.DefaultItems - this.application = await Factory.createInitAppWithFakeCrypto() + this.context = await Factory.createAppContext() + await this.context.launch() + this.application = this.context.application }) afterEach(async function () { diff --git a/packages/snjs/mocha/model_tests/notes_tags_folders.test.js b/packages/snjs/mocha/model_tests/notes_tags_folders.test.js index 0e3cedef6..f63bd8e55 100644 --- a/packages/snjs/mocha/model_tests/notes_tags_folders.test.js +++ b/packages/snjs/mocha/model_tests/notes_tags_folders.test.js @@ -7,7 +7,9 @@ const expect = chai.expect describe('tags as folders', () => { beforeEach(async function () { - this.application = await Factory.createInitAppWithFakeCrypto() + this.context = await Factory.createAppContext() + await this.context.launch() + this.application = this.context.application }) afterEach(async function () { diff --git a/packages/snjs/mocha/preferences.test.js b/packages/snjs/mocha/preferences.test.js index e989b8685..346a1ab89 100644 --- a/packages/snjs/mocha/preferences.test.js +++ b/packages/snjs/mocha/preferences.test.js @@ -7,7 +7,11 @@ const expect = chai.expect describe('preferences', function () { beforeEach(async function () { localStorage.clear() - this.application = await Factory.createInitAppWithFakeCrypto() + + this.context = await Factory.createAppContext() + await this.context.launch() + this.application = this.context.application + this.email = UuidGenerator.GenerateUuid() this.password = UuidGenerator.GenerateUuid() }) diff --git a/packages/snjs/mocha/singletons.test.js b/packages/snjs/mocha/singletons.test.js index e8fc0b7b3..e5a1c8d9c 100644 --- a/packages/snjs/mocha/singletons.test.js +++ b/packages/snjs/mocha/singletons.test.js @@ -31,7 +31,11 @@ describe('singletons', function () { beforeEach(async function () { localStorage.clear() this.expectedItemCount = BaseItemCounts.DefaultItems - this.application = await Factory.createInitAppWithFakeCrypto() + + this.context = await Factory.createAppContext() + await this.context.launch() + this.application = this.context.application + this.email = UuidGenerator.GenerateUuid() this.password = UuidGenerator.GenerateUuid() this.registerUser = async () => { diff --git a/packages/snjs/mocha/storage.test.js b/packages/snjs/mocha/storage.test.js index d18e3f4a9..38fb680f7 100644 --- a/packages/snjs/mocha/storage.test.js +++ b/packages/snjs/mocha/storage.test.js @@ -16,7 +16,9 @@ describe('storage manager', function () { beforeEach(async function () { localStorage.clear() this.expectedKeyCount = BASE_KEY_COUNT - this.application = await Factory.createInitAppWithFakeCrypto(Environment.Mobile) + this.context = await Factory.createAppContext() + await this.context.launch() + this.application = this.context.application this.email = UuidGenerator.GenerateUuid() this.password = UuidGenerator.GenerateUuid() }) @@ -221,7 +223,8 @@ describe('storage manager', function () { expect(decrypted.content).to.be.an.instanceof(Object) }) - it('disabling storage encryption should store items without encryption', async function () { + /** @TODO: Storage encryption disable is no longer available, remove tests and associated functionality */ + it.skip('disabling storage encryption should store items without encryption', async function () { await Factory.registerUserToApplication({ application: this.application, email: this.email, diff --git a/packages/snjs/mocha/sync_tests/offline.test.js b/packages/snjs/mocha/sync_tests/offline.test.js index 037a2a3f7..25cf65028 100644 --- a/packages/snjs/mocha/sync_tests/offline.test.js +++ b/packages/snjs/mocha/sync_tests/offline.test.js @@ -13,7 +13,9 @@ describe('offline syncing', () => { beforeEach(async function () { this.expectedItemCount = BaseItemCounts.DefaultItems - this.application = await Factory.createInitAppWithFakeCrypto() + this.context = await Factory.createAppContext() + await this.context.launch() + this.application = this.context.application }) afterEach(async function () { diff --git a/packages/snjs/mocha/sync_tests/online.test.js b/packages/snjs/mocha/sync_tests/online.test.js index eea5e9f7d..ffbbda1cd 100644 --- a/packages/snjs/mocha/sync_tests/online.test.js +++ b/packages/snjs/mocha/sync_tests/online.test.js @@ -95,7 +95,7 @@ describe('online syncing', function () { await this.application.sync.sync(syncOptions) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + this.application = await this.context.signout() expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems) diff --git a/packages/snjs/mocha/upgrading.test.js b/packages/snjs/mocha/upgrading.test.js index 78cf9525a..cbe08696b 100644 --- a/packages/snjs/mocha/upgrading.test.js +++ b/packages/snjs/mocha/upgrading.test.js @@ -7,7 +7,11 @@ const expect = chai.expect describe('upgrading', () => { beforeEach(async function () { localStorage.clear() - this.application = await Factory.createInitAppWithFakeCrypto() + + this.context = await Factory.createAppContext() + await this.context.launch() + + this.application = this.context.application this.email = UuidGenerator.GenerateUuid() this.password = UuidGenerator.GenerateUuid() this.passcode = '1234' @@ -170,7 +174,7 @@ describe('upgrading', () => { /** Delete default items key that is created on launch */ const itemsKey = await this.application.protocolService.getSureDefaultItemsKey() await this.application.itemManager.setItemToBeDeleted(itemsKey) - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(0) + expect(Uuids(this.application.itemManager.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false) Factory.createMappedNote(this.application) diff --git a/packages/utils/src/Domain/Utils/Utils.ts b/packages/utils/src/Domain/Utils/Utils.ts index edb3b867f..eff23e62d 100644 --- a/packages/utils/src/Domain/Utils/Utils.ts +++ b/packages/utils/src/Domain/Utils/Utils.ts @@ -519,9 +519,9 @@ export function truncateHexString(string: string, desiredBits: number) { * When awaited, this function allows code execution to pause for a set time. * Should be used primarily for testing. */ -export async function sleep(milliseconds: number, warn = true): Promise { +export async function sleep(milliseconds: number, warn = true, desc = ''): Promise { if (warn) { - console.warn(`Sleeping for ${milliseconds}ms`) + console.warn(`Sleeping for ${milliseconds}ms ${desc}`) } return new Promise((resolve) => { setTimeout(function () { diff --git a/packages/web/src/javascripts/Application/Application.ts b/packages/web/src/javascripts/Application/Application.ts index efd05875b..692fbf18d 100644 --- a/packages/web/src/javascripts/Application/Application.ts +++ b/packages/web/src/javascripts/Application/Application.ts @@ -78,7 +78,11 @@ export class WebApplication extends SNApplication implements WebApplicationInter appVersion: deviceInterface.appVersion, webSocketUrl: webSocketUrl, loadBatchSize: - deviceInterface.environment === Environment.Mobile ? 100 : ApplicationOptionsDefaults.loadBatchSize, + deviceInterface.environment === Environment.Mobile ? 250 : ApplicationOptionsDefaults.loadBatchSize, + sleepBetweenBatches: + deviceInterface.environment === Environment.Mobile ? 250 : ApplicationOptionsDefaults.sleepBetweenBatches, + allowMultipleSelection: deviceInterface.environment !== Environment.Mobile, + allowNoteSelectionStatePersistence: deviceInterface.environment !== Environment.Mobile, }) makeObservable(this, { diff --git a/packages/web/src/javascripts/Components/AccountMenu/GeneralAccountMenu.tsx b/packages/web/src/javascripts/Components/AccountMenu/GeneralAccountMenu.tsx index 05c4b0d54..81060f20c 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/GeneralAccountMenu.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/GeneralAccountMenu.tsx @@ -14,7 +14,6 @@ import { ApplicationGroup } from '@/Application/ApplicationGroup' import { formatLastSyncDate } from '@/Utils/DateUtils' import Spinner from '@/Components/Spinner/Spinner' import { MenuItemIconSize } from '@/Constants/TailwindClassNames' -import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' type Props = { viewControllerManager: ViewControllerManager @@ -85,22 +84,9 @@ const GeneralAccountMenu: FunctionComponent = ({ setMenuPane(AccountMenuPane.SignIn) }, [setMenuPane]) - const openFileSend = useCallback(() => { - const link = 'https://filesend.standardnotes.com/' - - if (application.isNativeMobileWeb()) { - application.mobileDevice().openUrl(link) - return - } - - window.open(link, '_blank') - }, [application]) - const CREATE_ACCOUNT_INDEX = 1 const SWITCHER_INDEX = 0 - const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) - return ( <>
@@ -186,12 +172,6 @@ const GeneralAccountMenu: FunctionComponent = ({
v{application.version} - {isMobileScreen && ( - - - Open FileSend - - )} viewControllerManager.isImportModalVisible.set(true)}> Import diff --git a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx index 78db68a34..cf42f5aa2 100644 --- a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx +++ b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx @@ -89,7 +89,7 @@ const ApplicationView: FunctionComponent = ({ application, mainApplicatio return } - void application.sessions.populateSessionFromDemoShareToken(token) + void application.user.populateSessionFromDemoShareToken(token) }, [application]) const onAppLaunch = useCallback(() => { diff --git a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.spec.ts b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.spec.ts index 8e3b13c9d..2dde45732 100644 --- a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.spec.ts +++ b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.spec.ts @@ -11,15 +11,17 @@ import { ItemListController } from './ItemListController' import { ItemsReloadSource } from './ItemsReloadSource' describe('item list controller', () => { + let application: WebApplication let controller: ItemListController let navigationController: NavigationController let selectionController: SelectedItemsController beforeEach(() => { - const application = {} as jest.Mocked + application = {} as jest.Mocked application.streamItems = jest.fn() application.addEventObserver = jest.fn() application.addWebEventObserver = jest.fn() + application.isNativeMobileWeb = jest.fn().mockReturnValue(false) navigationController = {} as jest.Mocked selectionController = {} as jest.Mocked @@ -50,6 +52,12 @@ describe('item list controller', () => { }) }) + it('should return false is platform is native mobile web', () => { + application.isNativeMobileWeb = jest.fn().mockReturnValue(true) + + expect(controller.shouldSelectFirstItem(ItemsReloadSource.TagChange)).toBe(false) + }) + it('should return false first item is file', () => { controller.getFirstNonProtectedItem = jest.fn().mockReturnValue({ content_type: ContentType.File, diff --git a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts index ff5586a5c..ac38d1e60 100644 --- a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts +++ b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts @@ -414,6 +414,10 @@ export class ItemListController extends AbstractViewController implements Intern } shouldSelectFirstItem = (itemsReloadSource: ItemsReloadSource) => { + if (this.application.isNativeMobileWeb()) { + return false + } + const item = this.getFirstNonProtectedItem() if (item && isFile(item)) { return false diff --git a/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts b/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts index 455618806..9726fd697 100644 --- a/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts +++ b/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts @@ -196,10 +196,13 @@ export class NavigationController } hydrateFromPersistedValue = (state: NavigationControllerPersistableValue | undefined) => { - if (!state) { + const uuidsToPreventHydrationOf: string[] = [SystemViewId.Files] + + if (!state || uuidsToPreventHydrationOf.includes(state.selectedTagUuid)) { void this.selectHomeNavigationView() return } + if (state.selectedTagUuid) { this.selectedUuid = state.selectedTagUuid this.selectHydratedTagOrDefault() diff --git a/packages/web/src/javascripts/Controllers/SelectedItemsController.ts b/packages/web/src/javascripts/Controllers/SelectedItemsController.ts index 7b74c77f2..b832481af 100644 --- a/packages/web/src/javascripts/Controllers/SelectedItemsController.ts +++ b/packages/web/src/javascripts/Controllers/SelectedItemsController.ts @@ -11,6 +11,7 @@ import { InternalEventBus, isFile, Uuids, + isNote, } from '@standardnotes/snjs' import { SelectionControllerPersistableValue } from '@standardnotes/ui-services' import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx' @@ -77,8 +78,14 @@ export class SelectedItemsController if (!state) { return } + if (!this.selectedUuids.size && state.selectedUuids.length > 0) { - void this.selectUuids(state.selectedUuids) + if (!this.application.options.allowNoteSelectionStatePersistence) { + const items = this.application.items.findItems(state.selectedUuids).filter((item) => !isNote(item)) + void this.selectUuids(Uuids(items)) + } else { + void this.selectUuids(state.selectedUuids) + } } } @@ -264,20 +271,22 @@ export class SelectedItemsController log(LoggingDomain.Selection, 'Select item', item.uuid) + const supportsMultipleSelection = this.application.options.allowMultipleSelection const hasMeta = this.keyboardService.activeModifiers.has(KeyboardModifier.Meta) const hasCtrl = this.keyboardService.activeModifiers.has(KeyboardModifier.Ctrl) const hasShift = this.keyboardService.activeModifiers.has(KeyboardModifier.Shift) const hasMoreThanOneSelected = this.selectedItemsCount > 1 const isAuthorizedForAccess = await this.application.protections.authorizeItemAccess(item) - if (userTriggered && (hasMeta || hasCtrl)) { + if (supportsMultipleSelection && userTriggered && (hasMeta || hasCtrl)) { if (this.selectedUuids.has(uuid) && hasMoreThanOneSelected) { this.removeSelectedItem(uuid) } else if (isAuthorizedForAccess) { - this.setSelectedUuids(this.selectedUuids.add(uuid)) + this.selectedUuids.add(uuid) + this.setSelectedUuids(this.selectedUuids) this.lastSelectedItem = item } - } else if (userTriggered && hasShift) { + } else if (supportsMultipleSelection && userTriggered && hasShift) { await this.selectItemsRange({ selectedItem: item }) } else { const shouldSelectNote = hasMoreThanOneSelected || !this.selectedUuids.has(uuid)