feat: improve initial load performance on mobile (#2126)

This commit is contained in:
Mo
2023-01-03 14:15:45 -06:00
committed by GitHub
parent a447fa1ad7
commit 3c332a35f6
59 changed files with 868 additions and 3003 deletions

View File

@@ -110,12 +110,12 @@ describe('application instances', () => {
* app deinit. */
await Factory.sleep(MaximumWaitTime - 0.05)
/** Access any deviceInterface function */
app.diskStorageService.deviceInterface.getAllRawDatabasePayloads(app.identifier)
app.diskStorageService.deviceInterface.getAllDatabaseEntries(app.identifier)
})
await app.lock()
})
describe('signOut()', () => {
describe.skip('signOut()', () => {
let testNote1
let confirmAlert
let deinit

View File

@@ -59,9 +59,6 @@ describe('basic auth', function () {
expect(await this.application.protocolService.getRootKey()).to.not.be.ok
expect(this.application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyNone)
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
expect(rawPayloads.length).to.equal(BaseItemCounts.DefaultItems)
})
it('successfully signs in to registered account', async function () {

View File

@@ -664,12 +664,12 @@ describe('key recovery service', function () {
await Factory.awaitFunctionInvokation(appA.keyRecoveryService, 'handleDecryptionOfAllKeysMatchingCorrectRootKey')
/** Stored version of items key should use new root key */
const stored = (await appA.deviceInterface.getAllRawDatabasePayloads(appA.identifier)).find(
const stored = (await appA.deviceInterface.getAllDatabaseEntries(appA.identifier)).find(
(payload) => payload.uuid === newDefaultKey.uuid,
)
const storedParams = await appA.protocolService.getKeyEmbeddedKeyParams(new EncryptedPayload(stored))
const correctStored = (await appB.deviceInterface.getAllRawDatabasePayloads(appB.identifier)).find(
const correctStored = (await appB.deviceInterface.getAllDatabaseEntries(appB.identifier)).find(
(payload) => payload.uuid === newDefaultKey.uuid,
)

View File

@@ -303,7 +303,8 @@ export function tomorrow() {
return new Date(new Date().setDate(new Date().getDate() + 1))
}
export async function sleep(seconds) {
export async function sleep(seconds, reason) {
console.log('Sleeping for reason', reason)
return Utils.sleep(seconds)
}

View File

@@ -21,17 +21,6 @@ export default class WebDeviceInterface {
}
}
async getAllRawStorageKeyValues() {
const results = []
for (const key of Object.keys(localStorage)) {
results.push({
key: key,
value: localStorage[key],
})
}
return results
}
async setRawStorageValue(key, value) {
localStorage.setItem(key, value)
}
@@ -60,7 +49,7 @@ export default class WebDeviceInterface {
return `${this._getDatabaseKeyPrefix(identifier)}${id}`
}
async getAllRawDatabasePayloads(identifier) {
async getAllDatabaseEntries(identifier) {
const models = []
for (const key in localStorage) {
if (key.startsWith(this._getDatabaseKeyPrefix(identifier))) {
@@ -70,21 +59,51 @@ export default class WebDeviceInterface {
return models
}
async saveRawDatabasePayload(payload, identifier) {
async getDatabaseLoadChunks(options, identifier) {
const entries = await this.getAllDatabaseEntries(identifier)
const sorted = GetSortedPayloadsByPriority(entries, options)
const itemsKeysChunk = {
entries: sorted.itemsKeyPayloads,
}
const contentTypePriorityChunk = {
entries: sorted.contentTypePriorityPayloads,
}
const remainingPayloadsChunks = []
for (let i = 0; i < sorted.remainingPayloads.length; i += options.batchSize) {
remainingPayloadsChunks.push({
entries: sorted.remainingPayloads.slice(i, i + options.batchSize),
})
}
const result = {
fullEntries: {
itemsKeys: itemsKeysChunk,
remainingChunks: [contentTypePriorityChunk, ...remainingPayloadsChunks],
},
remainingChunksItemCount: sorted.contentTypePriorityPayloads.length + sorted.remainingPayloads.length,
}
return result
}
async saveDatabaseEntry(payload, identifier) {
localStorage.setItem(this._keyForPayloadId(payload.uuid, identifier), JSON.stringify(payload))
}
async saveRawDatabasePayloads(payloads, identifier) {
async saveDatabaseEntries(payloads, identifier) {
for (const payload of payloads) {
await this.saveRawDatabasePayload(payload, identifier)
await this.saveDatabaseEntry(payload, identifier)
}
}
async removeRawDatabasePayloadWithId(id, identifier) {
async removeDatabaseEntry(id, identifier) {
localStorage.removeItem(this._keyForPayloadId(id, identifier))
}
async removeAllRawDatabasePayloads(identifier) {
async removeAllDatabaseEntries(identifier) {
for (const key in localStorage) {
if (key.startsWith(this._getDatabaseKeyPrefix(identifier))) {
delete localStorage[key]
@@ -124,12 +143,6 @@ export default class WebDeviceInterface {
localStorage.setItem(KEYCHAIN_STORAGE_KEY, JSON.stringify(keychain))
}
/** Allows unit tests to set legacy keychain structure as it was <= 003 */
// eslint-disable-next-line camelcase
async setLegacyRawKeychainValue(value) {
localStorage.setItem(KEYCHAIN_STORAGE_KEY, JSON.stringify(value))
}
async getRawKeychainValue() {
const keychain = localStorage.getItem(KEYCHAIN_STORAGE_KEY)
return JSON.parse(keychain)
@@ -139,19 +152,13 @@ export default class WebDeviceInterface {
localStorage.removeItem(KEYCHAIN_STORAGE_KEY)
}
performSoftReset() {
performSoftReset() {}
}
performHardReset() {
}
performHardReset() {}
isDeviceDestroyed() {
return false
}
deinit() {
}
deinit() {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,584 +0,0 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from '../lib/factory.js'
import FakeWebCrypto from '../lib/fake_web_crypto.js'
chai.use(chaiAsPromised)
const expect = chai.expect
describe('2020-01-15 web migration', () => {
beforeEach(() => {
localStorage.clear()
})
afterEach(() => {
localStorage.clear()
})
/**
* This test will pass but sync afterwards will not be successful
* as we are using a random value for the legacy session token
*/
it('2020-01-15 migration with passcode and account', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Create legacy migrations value so that base migration detects old app */
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
const identifier = 'foo'
const passcode = 'bar'
/** Create old version passcode parameters */
const passcodeKey = await operator003.createRootKey(identifier, passcode)
await application.deviceInterface.setRawStorageValue(
'offlineParams',
JSON.stringify(passcodeKey.keyParams.getPortableValue()),
)
/** Create arbitrary storage values and make sure they're migrated */
const arbitraryValues = {
foo: 'bar',
zar: 'tar',
har: 'car',
}
for (const key of Object.keys(arbitraryValues)) {
await application.deviceInterface.setRawStorageValue(key, arbitraryValues[key])
}
/** Create old version account parameters */
const password = 'tar'
const accountKey = await operator003.createRootKey(identifier, password)
/** Create legacy storage and encrypt it with passcode */
const embeddedStorage = {
mk: accountKey.masterKey,
ak: accountKey.dataAuthenticationKey,
pw: accountKey.serverPassword,
jwt: 'anything',
/** Legacy versions would store json strings inside of embedded storage */
auth_params: JSON.stringify(accountKey.keyParams.getPortableValue()),
}
const storagePayload = new DecryptedPayload({
uuid: await operator003.crypto.generateUUID(),
content_type: ContentType.EncryptedStorage,
content: {
storage: embeddedStorage,
},
})
const encryptionParams = await operator003.generateEncryptedParametersAsync(storagePayload, passcodeKey)
const persistPayload = new EncryptedPayload({ ...storagePayload, ...encryptionParams })
await application.deviceInterface.setRawStorageValue('encryptedStorage', JSON.stringify(persistPayload))
/** Create encrypted item and store it in db */
const notePayload = Factory.createNotePayload()
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, accountKey)
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
/** Run migration */
await application.prepareForLaunch({
receiveChallenge: async (challenge) => {
application.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], passcode)])
},
})
await application.launch(true)
expect(application.sessionManager.online()).to.equal(true)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyPlusWrapper)
/** Should be decrypted */
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
const valueStore = application.diskStorageService.values[storageMode]
expect(valueStore.content_type).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('offlineParams')).to.not.be.ok
const keyParams = await application.diskStorageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
expect(typeof keyParams).to.equal('object')
/** Embedded value should match */
const migratedKeyParams = await application.diskStorageService.getValue(
StorageKey.RootKeyParams,
StorageValueModes.Nonwrapped,
)
expect(migratedKeyParams).to.eql(JSON.parse(embeddedStorage.auth_params))
const rootKey = await application.protocolService.getRootKey()
expect(rootKey.masterKey).to.equal(accountKey.masterKey)
expect(rootKey.dataAuthenticationKey).to.equal(accountKey.dataAuthenticationKey)
/** Application should not retain server password from legacy versions */
expect(rootKey.serverPassword).to.not.be.ok
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V003)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyPlusWrapper)
/** Expect note is decrypted */
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
/** Ensure arbitrary values have been migrated */
for (const key of Object.keys(arbitraryValues)) {
const value = await application.diskStorageService.getValue(key)
expect(arbitraryValues[key]).to.equal(value)
}
console.warn('Expecting exception due to deiniting application while trying to renew session')
await Factory.safeDeinit(application)
}).timeout(15000)
it('2020-01-15 migration with passcode only', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Create legacy migrations value so that base migration detects old app */
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
const identifier = 'foo'
const passcode = 'bar'
/** Create old version passcode parameters */
const passcodeKey = await operator003.createRootKey(identifier, passcode)
await application.deviceInterface.setRawStorageValue(
'offlineParams',
JSON.stringify(passcodeKey.keyParams.getPortableValue()),
)
/** Create arbitrary storage values and make sure they're migrated */
const arbitraryValues = {
foo: 'bar',
zar: 'tar',
har: 'car',
}
for (const key of Object.keys(arbitraryValues)) {
await application.deviceInterface.setRawStorageValue(key, arbitraryValues[key])
}
const embeddedStorage = {
...arbitraryValues,
}
const storagePayload = new DecryptedPayload({
uuid: await operator003.crypto.generateUUID(),
content: {
storage: embeddedStorage,
},
content_type: ContentType.EncryptedStorage,
})
const encryptionParams = await operator003.generateEncryptedParametersAsync(storagePayload, passcodeKey)
const persistPayload = new EncryptedPayload({ ...storagePayload, ...encryptionParams })
await application.deviceInterface.setRawStorageValue('encryptedStorage', JSON.stringify(persistPayload))
/** Create encrypted item and store it in db */
const notePayload = Factory.createNotePayload()
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, passcodeKey)
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
await application.prepareForLaunch({
receiveChallenge: async (challenge) => {
application.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], passcode)])
},
})
await application.launch(true)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.WrapperOnly)
/** Should be decrypted */
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
const valueStore = application.diskStorageService.values[storageMode]
expect(valueStore.content_type).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('offlineParams')).to.not.be.ok
/** Embedded value should match */
const migratedKeyParams = await application.diskStorageService.getValue(
StorageKey.RootKeyParams,
StorageValueModes.Nonwrapped,
)
expect(migratedKeyParams).to.eql(embeddedStorage.auth_params)
const rootKey = await application.protocolService.getRootKey()
expect(rootKey.masterKey).to.equal(passcodeKey.masterKey)
expect(rootKey.dataAuthenticationKey).to.equal(passcodeKey.dataAuthenticationKey)
/** Root key is in memory with passcode only, so server password can be defined */
expect(rootKey.serverPassword).to.be.ok
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V003)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.WrapperOnly)
/** Expect note is decrypted */
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
/** Ensure arbitrary values have been migrated */
for (const key of Object.keys(arbitraryValues)) {
const value = await application.diskStorageService.getValue(key)
expect(arbitraryValues[key]).to.equal(value)
}
await Factory.safeDeinit(application)
})
/**
* This test will pass but sync afterwards will not be successful
* as we are using a random value for the legacy session token
*/
it('2020-01-15 migration with account only', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Create legacy migrations value so that base migration detects old app */
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
const identifier = 'foo'
/** Create old version account parameters */
const password = 'tar'
const accountKey = await operator003.createRootKey(identifier, password)
/** Create arbitrary storage values and make sure they're migrated */
const storage = {
foo: 'bar',
zar: 'tar',
har: 'car',
mk: accountKey.masterKey,
ak: accountKey.dataAuthenticationKey,
pw: accountKey.serverPassword,
jwt: 'anything',
/** Legacy versions would store json strings inside of embedded storage */
auth_params: JSON.stringify(accountKey.keyParams.getPortableValue()),
}
for (const key of Object.keys(storage)) {
await application.deviceInterface.setRawStorageValue(key, storage[key])
}
/** Create encrypted item and store it in db */
const notePayload = Factory.createNotePayload()
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, accountKey)
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
/** Run migration */
const promptValueReply = (prompts) => {
const values = []
for (const prompt of prompts) {
if (prompt.validation === ChallengeValidation.LocalPasscode) {
values.push(CreateChallengeValue(prompt, passcode))
} else {
/** We will be prompted to reauthetnicate our session, not relevant to this test
* but pass any value to avoid exception
*/
values.push(CreateChallengeValue(prompt, 'foo'))
}
}
return values
}
const receiveChallenge = async (challenge) => {
application.addChallengeObserver(challenge, {
onInvalidValue: (value) => {
const values = promptValueReply([value.prompt])
application.submitValuesForChallenge(challenge, values)
},
})
const initialValues = promptValueReply(challenge.prompts)
application.submitValuesForChallenge(challenge, initialValues)
}
await application.prepareForLaunch({
receiveChallenge: receiveChallenge,
})
await application.launch(true)
expect(application.sessionManager.online()).to.equal(true)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyOnly)
/** Should be decrypted */
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
const valueStore = application.diskStorageService.values[storageMode]
expect(valueStore.content_type).to.not.be.ok
/** Embedded value should match */
const migratedKeyParams = await application.diskStorageService.getValue(
StorageKey.RootKeyParams,
StorageValueModes.Nonwrapped,
)
expect(migratedKeyParams).to.eql(accountKey.keyParams.getPortableValue())
const rootKey = await application.protocolService.getRootKey()
expect(rootKey).to.be.ok
expect(await application.deviceInterface.getRawStorageValue('migrations')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('auth_params')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('jwt')).to.not.be.ok
const keyParams = await application.diskStorageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
expect(typeof keyParams).to.equal('object')
expect(rootKey.masterKey).to.equal(accountKey.masterKey)
expect(rootKey.dataAuthenticationKey).to.equal(accountKey.dataAuthenticationKey)
expect(rootKey.serverPassword).to.not.be.ok
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V003)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyOnly)
/** Expect note is decrypted */
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
/** Ensure arbitrary values have been migrated */
for (const key of Object.keys(storage)) {
/** Is stringified in storage, but parsed in storageService */
if (key === 'auth_params') {
continue
}
const value = await application.diskStorageService.getValue(key)
expect(storage[key]).to.equal(value)
}
console.warn('Expecting exception due to deiniting application while trying to renew session')
await Factory.safeDeinit(application)
})
it('2020-01-15 migration with no account and no passcode', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Create legacy migrations value so that base migration detects old app */
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
/** Create arbitrary storage values and make sure they're migrated */
const storage = {
foo: 'bar',
zar: 'tar',
har: 'car',
}
for (const key of Object.keys(storage)) {
await application.deviceInterface.setRawStorageValue(key, storage[key])
}
/** Create item and store it in db */
const notePayload = Factory.createNotePayload()
await application.deviceInterface.saveRawDatabasePayload(notePayload.ejected(), application.identifier)
/** Run migration */
await application.prepareForLaunch({
receiveChallenge: (_challenge) => {
return null
},
})
await application.launch(true)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyNone)
/** Should be decrypted */
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
const valueStore = application.diskStorageService.values[storageMode]
expect(valueStore.content_type).to.not.be.ok
const rootKey = await application.protocolService.getRootKey()
expect(rootKey).to.not.be.ok
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyNone)
expect(await application.deviceInterface.getRawStorageValue('migrations')).to.not.be.ok
/** Expect note is decrypted */
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
/** Ensure arbitrary values have been migrated */
for (const key of Object.keys(storage)) {
const value = await application.diskStorageService.getValue(key)
expect(storage[key]).to.equal(value)
}
await Factory.safeDeinit(application)
})
/**
* This test will pass but sync afterwards will not be successful
* as we are using a random value for the legacy session token
*/
it('2020-01-15 migration from app v1.0.1 with account only', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Create legacy migrations value so that base migration detects old app */
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
const operator001 = new SNProtocolOperator001(new FakeWebCrypto())
const identifier = 'foo'
/** Create old version account parameters */
const password = 'tar'
const accountKey = await operator001.createRootKey(identifier, password)
/** Create arbitrary storage values and make sure they're migrated */
const storage = {
mk: accountKey.masterKey,
pw: accountKey.serverPassword,
jwt: 'anything',
/** Legacy versions would store json strings inside of embedded storage */
auth_params: JSON.stringify(accountKey.keyParams.getPortableValue()),
user: JSON.stringify({ uuid: 'anything', email: 'anything' }),
}
for (const key of Object.keys(storage)) {
await application.deviceInterface.setRawStorageValue(key, storage[key])
}
/** Create encrypted item and store it in db */
const notePayload = Factory.createNotePayload()
const noteEncryptionParams = await operator001.generateEncryptedParametersAsync(notePayload, accountKey)
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
/** Run migration */
const promptValueReply = (prompts) => {
const values = []
for (const prompt of prompts) {
/** We will be prompted to reauthetnicate our session, not relevant to this test
* but pass any value to avoid exception
*/
values.push(CreateChallengeValue(prompt, 'foo'))
}
return values
}
const receiveChallenge = async (challenge) => {
application.addChallengeObserver(challenge, {
onInvalidValue: (value) => {
const values = promptValueReply([value.prompt])
application.submitValuesForChallenge(challenge, values)
},
})
const initialValues = promptValueReply(challenge.prompts)
application.submitValuesForChallenge(challenge, initialValues)
}
await application.prepareForLaunch({
receiveChallenge: receiveChallenge,
})
await application.launch(true)
expect(application.sessionManager.online()).to.equal(true)
expect(application.sessionManager.getUser()).to.be.ok
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyOnly)
/** Should be decrypted */
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
const valueStore = application.diskStorageService.values[storageMode]
expect(valueStore.content_type).to.not.be.ok
/** Embedded value should match */
const migratedKeyParams = await application.diskStorageService.getValue(
StorageKey.RootKeyParams,
StorageValueModes.Nonwrapped,
)
expect(migratedKeyParams).to.eql(accountKey.keyParams.getPortableValue())
const rootKey = await application.protocolService.getRootKey()
expect(rootKey).to.be.ok
expect(await application.deviceInterface.getRawStorageValue('migrations')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('auth_params')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('jwt')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('ak')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('mk')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('pw')).to.not.be.ok
const keyParams = await application.diskStorageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
expect(typeof keyParams).to.equal('object')
expect(rootKey.masterKey).to.equal(accountKey.masterKey)
expect(rootKey.dataAuthenticationKey).to.equal(accountKey.dataAuthenticationKey)
expect(rootKey.serverPassword).to.not.be.ok
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V001)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyOnly)
/** Expect note is decrypted */
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
/** Ensure arbitrary values have been migrated */
for (const key of Object.keys(storage)) {
/** Is stringified in storage, but parsed in storageService */
const value = await application.diskStorageService.getValue(key)
if (key === 'auth_params') {
continue
} else if (key === 'user') {
expect(storage[key]).to.equal(JSON.stringify(value))
} else {
expect(storage[key]).to.equal(value)
}
}
await Factory.safeDeinit(application)
})
it('2020-01-15 migration from 002 app with account and passcode but missing offlineParams.version', async function () {
/**
* There was an issue where if the user had offlineParams but it was missing the version key,
* the user could not get past the passcode migration screen.
*/
const application = await Factory.createAppWithRandNamespace()
/** Create legacy migrations value so that base migration detects old app */
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
const operator002 = new SNProtocolOperator002(new FakeWebCrypto())
const identifier = 'foo'
const passcode = 'bar'
/** Create old version passcode parameters */
const passcodeKey = await operator002.createRootKey(identifier, passcode)
/** The primary chaos agent */
const offlineParams = passcodeKey.keyParams.getPortableValue()
omitInPlace(offlineParams, ['version'])
await application.deviceInterface.setRawStorageValue('offlineParams', JSON.stringify(offlineParams))
/** Create old version account parameters */
const password = 'tar'
const accountKey = await operator002.createRootKey(identifier, password)
/** Create legacy storage and encrypt it with passcode */
const embeddedStorage = {
mk: accountKey.masterKey,
ak: accountKey.dataAuthenticationKey,
pw: accountKey.serverPassword,
jwt: 'anything',
/** Legacy versions would store json strings inside of embedded storage */
auth_params: JSON.stringify(accountKey.keyParams.getPortableValue()),
user: JSON.stringify({ uuid: 'anything', email: 'anything' }),
}
const storagePayload = new DecryptedPayload({
uuid: await operator002.crypto.generateUUID(),
content_type: ContentType.EncryptedStorage,
content: {
storage: embeddedStorage,
},
})
const encryptionParams = await operator002.generateEncryptedParametersAsync(storagePayload, passcodeKey)
const persistPayload = new EncryptedPayload({ ...storagePayload, ...encryptionParams })
await application.deviceInterface.setRawStorageValue('encryptedStorage', JSON.stringify(persistPayload))
/** Create encrypted item and store it in db */
const notePayload = Factory.createNotePayload()
const noteEncryptionParams = await operator002.generateEncryptedParametersAsync(notePayload, accountKey)
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
/** Runs migration */
await application.prepareForLaunch({
receiveChallenge: async (challenge) => {
application.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], passcode)])
},
})
await application.launch(true)
expect(application.sessionManager.online()).to.equal(true)
expect(application.sessionManager.getUser()).to.be.ok
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyPlusWrapper)
/** Should be decrypted */
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
const valueStore = application.diskStorageService.values[storageMode]
expect(valueStore.content_type).to.not.be.ok
/** Embedded value should match */
const migratedKeyParams = await application.diskStorageService.getValue(
StorageKey.RootKeyParams,
StorageValueModes.Nonwrapped,
)
expect(migratedKeyParams).to.eql(accountKey.keyParams.getPortableValue())
const rootKey = await application.protocolService.getRootKey()
expect(rootKey).to.be.ok
expect(await application.deviceInterface.getRawStorageValue('migrations')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('auth_params')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('jwt')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('ak')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('mk')).to.not.be.ok
expect(await application.deviceInterface.getRawStorageValue('pw')).to.not.be.ok
const keyParams = await application.diskStorageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
expect(typeof keyParams).to.equal('object')
expect(rootKey.masterKey).to.equal(accountKey.masterKey)
expect(rootKey.dataAuthenticationKey).to.equal(accountKey.dataAuthenticationKey)
expect(rootKey.serverPassword).to.not.be.ok
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V002)
/** Expect note is decrypted */
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
await Factory.safeDeinit(application)
})
})

View File

@@ -3,7 +3,7 @@ chai.use(chaiAsPromised)
const expect = chai.expect
describe('migrations', () => {
const allMigrations = ['2.0.0', '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']
beforeEach(async () => {
localStorage.clear()
@@ -25,34 +25,13 @@ describe('migrations', () => {
})
it('should return correct required migrations if stored version is 2.0.0', async function () {
expect((await SNMigrationService.getRequiredMigrations('2.0.0')).length).to.equal(allMigrations.length - 1)
expect((await SNMigrationService.getRequiredMigrations('2.0.0')).length).to.equal(allMigrations.length)
})
it('should return 0 required migrations if stored version is futuristic', async function () {
expect((await SNMigrationService.getRequiredMigrations('100.0.1')).length).to.equal(0)
})
it('after running base migration, legacy structure should set version as 1.0.0', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Set up 1.0.0 structure with tell-tale storage key */
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
await application.migrationService.runBaseMigrationPreRun()
expect(await application.migrationService.getStoredSnjsVersion()).to.equal('1.0.0')
await Factory.safeDeinit(application)
})
it('after running base migration, 2.0.0 structure set version as 2.0.0', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Set up 2.0.0 structure with tell-tale storage key */
await application.deviceInterface.setRawStorageValue(
namespacedKey(application.identifier, 'last_migration_timestamp'),
'anything',
)
await application.migrationService.runBaseMigrationPreRun()
expect(await application.migrationService.getStoredSnjsVersion()).to.equal('2.0.0')
await Factory.safeDeinit(application)
})
it('after running base migration with no present storage values, should set version to current', async function () {
const application = await Factory.createAppWithRandNamespace()
await application.migrationService.runBaseMigrationPreRun()
@@ -60,18 +39,6 @@ describe('migrations', () => {
await Factory.safeDeinit(application)
})
it('after running all migrations from a 1.0.0 installation, should set stored version to current', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Set up 1.0.0 structure with tell-tale storage key */
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
await application.prepareForLaunch({
receiveChallenge: () => {},
})
await application.launch(true)
expect(await application.migrationService.getStoredSnjsVersion()).to.equal(SnjsVersion)
await Factory.safeDeinit(application)
})
it('after running all migrations from a 2.0.0 installation, should set stored version to current', async function () {
const application = await Factory.createAppWithRandNamespace()
/** Set up 2.0.0 structure with tell-tale storage key */
@@ -84,24 +51,6 @@ describe('migrations', () => {
await Factory.safeDeinit(application)
})
it('should be correct migration count coming from 1.0.0', async function () {
const application = await Factory.createAppWithRandNamespace()
await application.deviceInterface.setRawStorageValue('migrations', 'anything')
await application.migrationService.runBaseMigrationPreRun()
expect(await application.migrationService.getStoredSnjsVersion()).to.equal('1.0.0')
const pendingMigrations = await SNMigrationService.getRequiredMigrations(
await application.migrationService.getStoredSnjsVersion(),
)
expect(pendingMigrations.length).to.equal(allMigrations.length)
expect(pendingMigrations[0].version()).to.equal('2.0.0')
await application.prepareForLaunch({
receiveChallenge: () => {},
})
await application.launch(true)
expect(await application.migrationService.getStoredSnjsVersion()).to.equal(SnjsVersion)
await Factory.safeDeinit(application)
})
it('2.20.0 remove mfa migration', async function () {
const application = await Factory.createAppWithRandNamespace()

View File

@@ -735,7 +735,7 @@ describe('importing', function () {
}),
)
await application.deviceInterface.setRawStorageValue('standardnotes-snjs_version', '2.0.11')
await application.deviceInterface.saveRawDatabasePayload(
await application.deviceInterface.saveDatabaseEntry(
{
content:
'003:9f2c7527eb8b2a1f8bfb3ea6b885403b6886bce2640843ebd57a6c479cbf7597:58e3322b-269a-4be3-a658-b035dffcd70f:9140b23a0fa989e224e292049f133154:SESTNOgIGf2+ZqmJdFnGU4EMgQkhKOzpZNoSzx76SJaImsayzctAgbUmJ+UU2gSQAHADS3+Z5w11bXvZgIrStTsWriwvYkNyyKmUPadKHNSBwOk4WeBZpWsA9gtI5zgI04Q5pvb8hS+kNW2j1DjM4YWqd0JQxMOeOrMIrxr/6Awn5TzYE+9wCbXZdYHyvRQcp9ui/G02ZJ67IA86vNEdjTTBAAWipWqTqKH9VDZbSQ2W/IOKfIquB373SFDKZb1S1NmBFvcoG2G7w//fAl/+ehYiL6UdiNH5MhXCDAOTQRFNfOh57HFDWVnz1VIp8X+VAPy6d9zzQH+8aws1JxHq/7BOhXrFE8UCueV6kERt9njgQxKJzd9AH32ShSiUB9X/sPi0fUXbS178xAZMJrNx3w==:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=',

View File

@@ -94,7 +94,7 @@ describe('model manager mapping', () => {
const note = this.application.itemManager.getDisplayableNotes()[0]
await this.application.itemManager.setItemDirty(note)
const dirtyItems = this.application.itemManager.getDirtyItems()
expect(dirtyItems.length).to.equal(1)
expect(Uuids(dirtyItems).includes(note.uuid))
})
it('set all items dirty', async function () {

View File

@@ -642,7 +642,7 @@ describe('server session', function () {
await app2Deinit
const deviceInterface = new WebDeviceInterface()
const payloads = await deviceInterface.getAllRawDatabasePayloads(app2identifier)
const payloads = await deviceInterface.getAllDatabaseEntries(app2identifier)
expect(payloads).to.be.empty
})
@@ -670,7 +670,7 @@ describe('server session', function () {
await app2Deinit
const deviceInterface = new WebDeviceInterface()
const payloads = await deviceInterface.getAllRawDatabasePayloads(app2identifier)
const payloads = await deviceInterface.getAllDatabaseEntries(app2identifier)
expect(payloads).to.be.empty
})

View File

@@ -300,6 +300,7 @@ describe('storage manager', function () {
await Factory.createSyncedNote(this.application)
expect(await Factory.storagePayloadCount(this.application)).to.equal(BaseItemCounts.DefaultItems + 1)
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
await Factory.sleep(0.1, 'Allow all untrackable singleton syncs to complete')
expect(await Factory.storagePayloadCount(this.application)).to.equal(BaseItemCounts.DefaultItems)
})
})

View File

@@ -31,10 +31,7 @@ describe('offline syncing', () => {
it('should sync item with no passcode', async function () {
let note = await Factory.createMappedNote(this.application)
expect(this.application.itemManager.getDirtyItems().length).to.equal(1)
const rawPayloads1 = await this.application.diskStorageService.getAllRawPayloads()
expect(rawPayloads1.length).to.equal(this.expectedItemCount)
expect(Uuids(this.application.itemManager.getDirtyItems()).includes(note.uuid))
await this.application.syncService.sync(syncOptions)

View File

@@ -218,14 +218,21 @@ describe('online syncing', function () {
it('retrieving new items should not mark them as dirty', async function () {
const originalNote = await Factory.createSyncedNote(this.application)
this.expectedItemCount++
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
this.application.syncService.addEventObserver((event) => {
if (event === SyncEvent.SingleRoundTripSyncCompleted) {
const note = this.application.items.findItem(originalNote.uuid)
expect(note.dirty).to.not.be.ok
}
const promise = new Promise((resolve) => {
this.application.syncService.addEventObserver(async (event) => {
if (event === SyncEvent.SingleRoundTripSyncCompleted) {
const note = this.application.items.findItem(originalNote.uuid)
if (note) {
expect(note.dirty).to.not.be.ok
resolve()
}
}
})
})
await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true)
await promise
})
it('allows saving of data after sign out', async function () {
@@ -579,7 +586,7 @@ describe('online syncing', function () {
await this.application.itemManager.setItemDirty(note)
await this.application.syncService.sync(syncOptions)
this.expectedItemCount++
const rawPayloads = await this.application.syncService.getDatabasePayloads()
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
const notePayload = rawPayloads.find((p) => p.content_type === ContentType.Note)
expect(typeof notePayload.content).to.equal('string')
})
@@ -651,8 +658,7 @@ describe('online syncing', function () {
await this.application.syncService.clearSyncPositionTokens()
await this.application.payloadManager.resetState()
await this.application.itemManager.resetState()
const databasePayloads = await this.application.diskStorageService.getAllRawPayloads()
await this.application.syncService.loadDatabasePayloads(databasePayloads)
await this.application.syncService.loadDatabasePayloads()
await this.application.syncService.sync(syncOptions)
const newRawPayloads = await this.application.diskStorageService.getAllRawPayloads()
@@ -672,7 +678,9 @@ describe('online syncing', function () {
const payload = Factory.createStorageItemPayload(contentTypes[Math.floor(i / 2)])
originalPayloads.push(payload)
}
const { contentTypePriorityPayloads } = GetSortedPayloadsByPriority(originalPayloads, ['C', 'A', 'B'])
const { contentTypePriorityPayloads } = GetSortedPayloadsByPriority(originalPayloads, {
contentTypePriority: ['C', 'A', 'B'],
})
expect(contentTypePriorityPayloads[0].content_type).to.equal('C')
expect(contentTypePriorityPayloads[2].content_type).to.equal('A')
expect(contentTypePriorityPayloads[4].content_type).to.equal('B')
@@ -685,14 +693,10 @@ describe('online syncing', function () {
await this.application.syncService.sync(syncOptions)
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
expect(rawPayloads.length).to.equal(BaseItemCounts.DefaultItems)
await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true)
this.application.syncService.ut_setDatabaseLoaded(false)
const databasePayloads = await this.application.diskStorageService.getAllRawPayloads()
await this.application.syncService.loadDatabasePayloads(databasePayloads)
await this.application.syncService.loadDatabasePayloads()
await this.application.syncService.sync(syncOptions)
const items = await this.application.itemManager.items

View File

@@ -80,8 +80,6 @@
<script type="module" src="protection.test.js"></script>
<script type="module" src="singletons.test.js"></script>
<script type="module" src="migrations/migration.test.js"></script>
<script type="module" src="migrations/2020-01-15-web.test.js"></script>
<script type="module" src="migrations/2020-01-15-mobile.test.js"></script>
<script type="module" src="migrations/tags-to-folders.test.js"></script>
<script type="module" src="history.test.js"></script>
<script type="module" src="actions.test.js"></script>