diff --git a/packages/encryption/package.json b/packages/encryption/package.json index 8ec6140fb..9de58d740 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -39,7 +39,6 @@ "@standardnotes/common": "^1.23.1", "@standardnotes/models": "workspace:*", "@standardnotes/responses": "workspace:*", - "@standardnotes/services": "workspace:*", "@standardnotes/sncrypto-common": "workspace:*", "@standardnotes/utils": "workspace:*", "reflect-metadata": "^0.1.13" diff --git a/packages/encryption/src/Domain/Operator/OperatorWrapper.ts b/packages/encryption/src/Domain/Operator/OperatorWrapper.ts index b00092924..323d8dcb5 100644 --- a/packages/encryption/src/Domain/Operator/OperatorWrapper.ts +++ b/packages/encryption/src/Domain/Operator/OperatorWrapper.ts @@ -1,4 +1,10 @@ -import * as Models from '@standardnotes/models' +import { + DecryptedPayloadInterface, + ItemsKeyInterface, + RootKeyInterface, + ItemContent, + EncryptedPayloadInterface, +} from '@standardnotes/models' import { DecryptedParameters, EncryptedParameters, @@ -9,8 +15,8 @@ import { isAsyncOperator } from './Functions' import { OperatorManager } from './OperatorManager' export async function encryptPayload( - payload: Models.DecryptedPayloadInterface, - key: Models.ItemsKeyInterface | Models.RootKeyInterface, + payload: DecryptedPayloadInterface, + key: ItemsKeyInterface | RootKeyInterface, operatorManager: OperatorManager, ): Promise { const operator = operatorManager.operatorForVersion(key.keyVersion) @@ -29,9 +35,9 @@ export async function encryptPayload( return encryptionParameters } -export async function decryptPayload( - payload: Models.EncryptedPayloadInterface, - key: Models.ItemsKeyInterface | Models.RootKeyInterface, +export async function decryptPayload( + payload: EncryptedPayloadInterface, + key: ItemsKeyInterface | RootKeyInterface, operatorManager: OperatorManager, ): Promise | ErrorDecryptingParameters> { const operator = operatorManager.operatorForVersion(payload.version) diff --git a/packages/encryption/src/Domain/Service/RootKey/RootKeyServiceEvent.ts b/packages/encryption/src/Domain/Service/RootKey/RootKeyServiceEvent.ts new file mode 100644 index 000000000..4889f650c --- /dev/null +++ b/packages/encryption/src/Domain/Service/RootKey/RootKeyServiceEvent.ts @@ -0,0 +1,3 @@ +export enum RootKeyServiceEvent { + RootKeyStatusChanged = 'RootKeyStatusChanged', +} diff --git a/packages/encryption/src/Domain/Types/EncryptedParameters.d.ts b/packages/encryption/src/Domain/Types/EncryptedParameters.d.ts new file mode 100644 index 000000000..f8e7af11b --- /dev/null +++ b/packages/encryption/src/Domain/Types/EncryptedParameters.d.ts @@ -0,0 +1,22 @@ +import { ProtocolVersion } from '@standardnotes/common'; +import { EncryptedPayloadInterface, ItemContent } from '@standardnotes/models'; +export declare type EncryptedParameters = { + uuid: string; + content: string; + items_key_id: string | undefined; + enc_item_key: string; + version: ProtocolVersion; + /** @deprecated */ + auth_hash?: string; +}; +export declare type DecryptedParameters = { + uuid: string; + content: C; +}; +export declare type ErrorDecryptingParameters = { + uuid: string; + errorDecrypting: true; + waitingForKey?: boolean; +}; +export declare function isErrorDecryptingParameters(x: EncryptedParameters | DecryptedParameters | ErrorDecryptingParameters): x is ErrorDecryptingParameters; +export declare function encryptedParametersFromPayload(payload: EncryptedPayloadInterface): EncryptedParameters; diff --git a/packages/encryption/src/Domain/Types/EncryptedParameters.js b/packages/encryption/src/Domain/Types/EncryptedParameters.js new file mode 100644 index 000000000..a3a8acd68 --- /dev/null +++ b/packages/encryption/src/Domain/Types/EncryptedParameters.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.encryptedParametersFromPayload = exports.isErrorDecryptingParameters = void 0; +function isErrorDecryptingParameters(x) { + return x.errorDecrypting; +} +exports.isErrorDecryptingParameters = isErrorDecryptingParameters; +function encryptedParametersFromPayload(payload) { + return { + uuid: payload.uuid, + content: payload.content, + items_key_id: payload.items_key_id, + enc_item_key: payload.enc_item_key, + version: payload.version, + auth_hash: payload.auth_hash, + }; +} +exports.encryptedParametersFromPayload = encryptedParametersFromPayload; diff --git a/packages/encryption/src/Domain/index.ts b/packages/encryption/src/Domain/index.ts index 93adda627..272a7e3fe 100644 --- a/packages/encryption/src/Domain/index.ts +++ b/packages/encryption/src/Domain/index.ts @@ -1,5 +1,4 @@ export * from './Algorithm' -export * from './Backups/BackupFileDecryptor' export * from './Backups/BackupFileType' export * from './Keys/ItemsKey/ItemsKey' export * from './Keys/ItemsKey/ItemsKeyMutator' @@ -10,7 +9,6 @@ export * from './Keys/RootKey/ProtocolVersionForKeyParams' export * from './Keys/RootKey/RootKey' export * from './Keys/RootKey/RootKeyParams' export * from './Keys/RootKey/ValidKeyParamsKeys' -export * from './Keys/Utils/DecryptItemsKey' export * from './Keys/Utils/KeyRecoveryStrings' export * from './Operator/001/Operator001' export * from './Operator/002/Operator002' @@ -21,11 +19,9 @@ export * from './Operator/Operator' export * from './Operator/OperatorManager' export * from './Operator/OperatorWrapper' export * from './Service/Encryption/EncryptionProvider' -export * from './Service/Encryption/EncryptionService' export * from './Service/Functions' -export * from './Service/Items/ItemsEncryption' export * from './Service/RootKey/KeyMode' -export * from './Service/RootKey/RootKeyEncryption' +export * from './Service/RootKey/RootKeyServiceEvent' export * from './Split/AbstractKeySplit' export * from './Split/EncryptionSplit' export * from './Split/EncryptionTypeSplit' diff --git a/packages/filepicker/src/index.ts b/packages/filepicker/src/index.ts index eaf955f7f..0621e586f 100644 --- a/packages/filepicker/src/index.ts +++ b/packages/filepicker/src/index.ts @@ -5,4 +5,3 @@ export * from './Streaming/StreamingReader' export * from './Streaming/StreamingSaver' export * from './Streaming/StreamingApi' export * from './utils' -export * from './Cache/FileMemoryCache' diff --git a/packages/filepicker/src/Cache/FileMemoryCache.spec.ts b/packages/files/src/Domain/Cache/FileMemoryCache.spec.ts similarity index 97% rename from packages/filepicker/src/Cache/FileMemoryCache.spec.ts rename to packages/files/src/Domain/Cache/FileMemoryCache.spec.ts index e10d4b2e9..c46eb0b87 100644 --- a/packages/filepicker/src/Cache/FileMemoryCache.spec.ts +++ b/packages/files/src/Domain/Cache/FileMemoryCache.spec.ts @@ -1,5 +1,4 @@ -import { EncryptedBytes } from '@standardnotes/files' - +import { EncryptedBytes } from '../Types/EncryptedBytes' import { FileMemoryCache } from './FileMemoryCache' describe('file memory cache', () => { diff --git a/packages/filepicker/src/Cache/FileMemoryCache.ts b/packages/files/src/Domain/Cache/FileMemoryCache.ts similarity index 94% rename from packages/filepicker/src/Cache/FileMemoryCache.ts rename to packages/files/src/Domain/Cache/FileMemoryCache.ts index 83b8d13bb..c809992e2 100644 --- a/packages/filepicker/src/Cache/FileMemoryCache.ts +++ b/packages/files/src/Domain/Cache/FileMemoryCache.ts @@ -1,6 +1,7 @@ import { removeFromArray } from '@standardnotes/utils' import { Uuid } from '@standardnotes/common' -import { EncryptedBytes } from '@standardnotes/files' + +import { EncryptedBytes } from '../Types/EncryptedBytes' export class FileMemoryCache { private cache: Record = {} diff --git a/packages/files/src/Domain/index.ts b/packages/files/src/Domain/index.ts index 908e6c34c..954fc6d96 100644 --- a/packages/files/src/Domain/index.ts +++ b/packages/files/src/Domain/index.ts @@ -5,6 +5,7 @@ export * from './Api/FileSystemApi' export * from './Api/FileSystemNoSelection' export * from './Api/FileSystemResult' export * from './Api/FilesApiInterface' +export * from './Cache/FileMemoryCache' export * from './Chunker/ByteChunker' export * from './Chunker/OnChunkCallback' export * from './Chunker/OrderedByteChunker' diff --git a/packages/services/package.json b/packages/services/package.json index 6c986ebc6..b64e54b85 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -25,6 +25,7 @@ "dependencies": { "@standardnotes/auth": "^3.19.4", "@standardnotes/common": "^1.30.0", + "@standardnotes/encryption": "workspace:^", "@standardnotes/files": "workspace:^", "@standardnotes/models": "workspace:^", "@standardnotes/responses": "workspace:*", diff --git a/packages/services/src/Domain/Backups/BackupService.ts b/packages/services/src/Domain/Backups/BackupService.ts index 239005b3e..9bbb6ca96 100644 --- a/packages/services/src/Domain/Backups/BackupService.ts +++ b/packages/services/src/Domain/Backups/BackupService.ts @@ -2,8 +2,7 @@ import { ContentType, Uuid } from '@standardnotes/common' import { EncryptionProvider } from '@standardnotes/encryption' import { PayloadEmitSource, FileItem, CreateEncryptedBackupFileContextPayload } from '@standardnotes/models' import { ClientDisplayableError } from '@standardnotes/responses' -import { FileBackupMetadataFile, FileBackupsDevice, FileBackupsMapping } from '../Device/FileBackupsDevice' -import { FilesApiInterface } from '@standardnotes/files' +import { FilesApiInterface, FileBackupMetadataFile, FileBackupsDevice, FileBackupsMapping } from '@standardnotes/files' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { AbstractService } from '../Service/AbstractService' diff --git a/packages/services/src/Domain/Device/DesktopWebCommunication.ts b/packages/services/src/Domain/Device/DesktopWebCommunication.ts index b3447c20d..cb36dc560 100644 --- a/packages/services/src/Domain/Device/DesktopWebCommunication.ts +++ b/packages/services/src/Domain/Device/DesktopWebCommunication.ts @@ -1,5 +1,5 @@ import { DecryptedTransferPayload } from '@standardnotes/models' -import { FileBackupsDevice } from './FileBackupsDevice' +import { FileBackupsDevice } from '@standardnotes/files' export interface WebClientRequiresDesktopMethods extends FileBackupsDevice { localBackupsCount(): Promise diff --git a/packages/encryption/src/Domain/Backups/BackupFileDecryptor.ts b/packages/services/src/Domain/Encryption/BackupFileDecryptor.ts similarity index 94% rename from packages/encryption/src/Domain/Backups/BackupFileDecryptor.ts rename to packages/services/src/Domain/Encryption/BackupFileDecryptor.ts index 920699c78..7a985d4a0 100644 --- a/packages/encryption/src/Domain/Backups/BackupFileDecryptor.ts +++ b/packages/services/src/Domain/Encryption/BackupFileDecryptor.ts @@ -5,6 +5,15 @@ import { leftVersionGreaterThanOrEqualToRight, ProtocolVersion, } from '@standardnotes/common' +import { + BackupFileType, + ContentTypeUsesRootKeyEncryption, + CreateAnyKeyParams, + isItemsKey, + SNItemsKey, + SNRootKey, + SNRootKeyParams, +} from '@standardnotes/encryption' import { BackupFile, CreateDecryptedItemFromPayload, @@ -23,13 +32,7 @@ import { } from '@standardnotes/models' import { ClientDisplayableError } from '@standardnotes/responses' import { extendArray } from '@standardnotes/utils' -import { isItemsKey, SNItemsKey } from '../Keys/ItemsKey/ItemsKey' -import { ContentTypeUsesRootKeyEncryption } from '../Keys/RootKey/Functions' -import { CreateAnyKeyParams } from '../Keys/RootKey/KeyParamsFunctions' -import { SNRootKey } from '../Keys/RootKey/RootKey' -import { SNRootKeyParams } from '../Keys/RootKey/RootKeyParams' -import { EncryptionService } from '../Service/Encryption/EncryptionService' -import { BackupFileType } from './BackupFileType' +import { EncryptionService } from './EncryptionService' export async function DecryptBackupFile( file: BackupFile, diff --git a/packages/encryption/src/Domain/Service/Encryption/EncryptionService.ts b/packages/services/src/Domain/Encryption/EncryptionService.ts similarity index 77% rename from packages/encryption/src/Domain/Service/Encryption/EncryptionService.ts rename to packages/services/src/Domain/Encryption/EncryptionService.ts index 6df4dc899..e994198d3 100644 --- a/packages/encryption/src/Domain/Service/Encryption/EncryptionService.ts +++ b/packages/services/src/Domain/Encryption/EncryptionService.ts @@ -1,53 +1,77 @@ -import * as Common from '@standardnotes/common' -import * as Models from '@standardnotes/models' +import { + CreateAnyKeyParams, + CreateEncryptionSplitWithKeyLookup, + DecryptedParameters, + EncryptedParameters, + encryptedParametersFromPayload, + EncryptionProvider, + ErrorDecryptingParameters, + findDefaultItemsKey, + FindPayloadInDecryptionSplit, + FindPayloadInEncryptionSplit, + isErrorDecryptingParameters, + ItemAuthenticatedData, + KeyedDecryptionSplit, + KeyedEncryptionSplit, + KeyMode, + LegacyAttachedData, + OperatorManager, + RootKeyEncryptedAuthenticatedData, + RootKeyServiceEvent, + SNRootKey, + SNRootKeyParams, + SplitPayloadsByEncryptionType, + V001Algorithm, + V002Algorithm, +} from '@standardnotes/encryption' import { BackupFile, CreateDecryptedBackupFileContextPayload, CreateEncryptedBackupFileContextPayload, + DecryptedPayload, + DecryptedPayloadInterface, EncryptedPayload, + EncryptedPayloadInterface, isDecryptedPayload, isEncryptedPayload, + ItemContent, + ItemsKeyInterface, RootKeyInterface, } from '@standardnotes/models' import { ClientDisplayableError } from '@standardnotes/responses' -import * as Services from '@standardnotes/services' -import { DiagnosticInfo } from '@standardnotes/services' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' -import * as Utils from '@standardnotes/utils' -import { isNotUndefined } from '@standardnotes/utils' -import { V001Algorithm, V002Algorithm } from '../../Algorithm' -import { DecryptBackupFile } from '../../Backups/BackupFileDecryptor' -import { CreateAnyKeyParams } from '../../Keys/RootKey/KeyParamsFunctions' -import { SNRootKey } from '../../Keys/RootKey/RootKey' -import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams' -import { OperatorManager } from '../../Operator/OperatorManager' import { - CreateEncryptionSplitWithKeyLookup, - FindPayloadInDecryptionSplit, - FindPayloadInEncryptionSplit, -} from '../../Split/EncryptionSplit' -import { SplitPayloadsByEncryptionType } from '../../Split/Functions' -import { KeyedDecryptionSplit } from '../../Split/KeyedDecryptionSplit' -import { KeyedEncryptionSplit } from '../../Split/KeyedEncryptionSplit' + extendArray, + isNotUndefined, + isNullOrUndefined, + isReactNativeEnvironment, + isWebCryptoAvailable, + UuidGenerator, +} from '@standardnotes/utils' import { - DecryptedParameters, - EncryptedParameters, - encryptedParametersFromPayload, - ErrorDecryptingParameters, - isErrorDecryptingParameters, -} from '../../Types/EncryptedParameters' -import { ItemAuthenticatedData } from '../../Types/ItemAuthenticatedData' -import { LegacyAttachedData } from '../../Types/LegacyAttachedData' -import { RootKeyEncryptedAuthenticatedData } from '../../Types/RootKeyEncryptedAuthenticatedData' -import { findDefaultItemsKey } from '../Functions' -import { ItemsEncryptionService } from '../Items/ItemsEncryption' -import { KeyMode } from '../RootKey/KeyMode' -import * as RootKeyEncryption from '../RootKey/RootKeyEncryption' -import { EncryptionProvider } from './EncryptionProvider' + AnyKeyParamsContent, + ApplicationIdentifier, + compareVersions, + ContentType, + isVersionLessThanOrEqualTo, + KeyParamsOrigination, + ProtocolVersion, + ProtocolVersionLastNonrootItemsKey, + ProtocolVersionLatest, +} from '@standardnotes/common' -export enum EncryptionServiceEvent { - RootKeyStatusChanged = 'RootKeyStatusChanged', -} +import { AbstractService } from '../Service/AbstractService' +import { ItemsEncryptionService } from './ItemsEncryption' +import { ItemManagerInterface } from '../Item/ItemManagerInterface' +import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface' +import { DeviceInterface } from '../Device/DeviceInterface' +import { StorageServiceInterface } from '../Storage/StorageServiceInterface' +import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' +import { SyncEvent } from '../Event/SyncEvent' +import { DiagnosticInfo } from '../Diagnostics/ServiceDiagnostics' +import { RootKeyEncryptionService } from './RootKeyEncryption' +import { DecryptBackupFile } from './BackupFileDecryptor' +import { EncryptionServiceEvent } from './EncryptionServiceEvent' /** * The encryption service is responsible for the encryption and decryption of payloads, and @@ -76,20 +100,20 @@ export enum EncryptionServiceEvent { * It also exposes public methods that allows consumers to retrieve an items key * for a particular payload, and also retrieve all available items keys. */ -export class EncryptionService extends Services.AbstractService implements EncryptionProvider { +export class EncryptionService extends AbstractService implements EncryptionProvider { private operatorManager: OperatorManager private readonly itemsEncryption: ItemsEncryptionService - private readonly rootKeyEncryption: RootKeyEncryption.RootKeyEncryptionService + private readonly rootKeyEncryption: RootKeyEncryptionService private rootKeyObserverDisposer: () => void constructor( - private itemManager: Services.ItemManagerInterface, - private payloadManager: Services.PayloadManagerInterface, - public deviceInterface: Services.DeviceInterface, - private storageService: Services.StorageServiceInterface, - private identifier: Common.ApplicationIdentifier, + private itemManager: ItemManagerInterface, + private payloadManager: PayloadManagerInterface, + public deviceInterface: DeviceInterface, + private storageService: StorageServiceInterface, + private identifier: ApplicationIdentifier, public crypto: PureCryptoInterface, - protected override internalEventBus: Services.InternalEventBusInterface, + protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) this.crypto = crypto @@ -104,7 +128,7 @@ export class EncryptionService extends Services.AbstractService { this.itemsEncryption.userVersion = this.getUserVersion() - if (event === RootKeyEncryption.RootKeyServiceEvent.RootKeyStatusChanged) { + if (event === RootKeyServiceEvent.RootKeyStatusChanged) { void this.notifyEvent(EncryptionServiceEvent.RootKeyStatusChanged) } }) - Utils.UuidGenerator.SetGenerator(this.crypto.generateUUID) + UuidGenerator.SetGenerator(this.crypto.generateUUID) } public override deinit(): void { @@ -160,7 +184,7 @@ export class EncryptionService extends Services.AbstractService { @@ -201,22 +225,22 @@ export class EncryptionService extends Services.AbstractService { + public async encryptSplitSingle(split: KeyedEncryptionSplit): Promise { return (await this.encryptSplit(split))[0] } - public async encryptSplit(split: KeyedEncryptionSplit): Promise { + public async encryptSplit(split: KeyedEncryptionSplit): Promise { const allEncryptedParams: EncryptedParameters[] = [] if (split.usesRootKey) { @@ -224,7 +248,7 @@ export class EncryptionService extends Services.AbstractService { @@ -263,17 +287,17 @@ export class EncryptionService extends Services.AbstractService = Models.DecryptedPayloadInterface, - >(split: KeyedDecryptionSplit): Promise

{ + C extends ItemContent = ItemContent, + P extends DecryptedPayloadInterface = DecryptedPayloadInterface, + >(split: KeyedDecryptionSplit): Promise

{ const results = await this.decryptSplit(split) return results[0] } public async decryptSplit< - C extends Models.ItemContent = Models.ItemContent, - P extends Models.DecryptedPayloadInterface = Models.DecryptedPayloadInterface, - >(split: KeyedDecryptionSplit): Promise<(P | Models.EncryptedPayloadInterface)[]> { + C extends ItemContent = ItemContent, + P extends DecryptedPayloadInterface = DecryptedPayloadInterface, + >(split: KeyedDecryptionSplit): Promise<(P | EncryptedPayloadInterface)[]> { const resultParams: (DecryptedParameters | ErrorDecryptingParameters)[] = [] if (split.usesRootKey) { @@ -281,14 +305,14 @@ export class EncryptionService extends Services.AbstractService( split.usesRootKeyWithKeyLookup.items, ) - Utils.extendArray(resultParams, rootKeyDecrypted) + extendArray(resultParams, rootKeyDecrypted) } if (split.usesItemsKey) { @@ -296,26 +320,26 @@ export class EncryptionService extends Services.AbstractService( split.usesItemsKeyWithKeyLookup.items, ) - Utils.extendArray(resultParams, itemsKeyDecrypted) + extendArray(resultParams, itemsKeyDecrypted) } const packagedResults = resultParams.map((params) => { const original = FindPayloadInDecryptionSplit(params.uuid, split) if (isErrorDecryptingParameters(params)) { - return new Models.EncryptedPayload({ + return new EncryptedPayload({ ...original.ejected(), ...params, }) } else { - return new Models.DecryptedPayload({ + return new DecryptedPayload({ ...original.ejected(), ...params, }) as P @@ -333,7 +357,7 @@ export class EncryptionService extends Services.AbstractService= 0) { + if (compareVersions(keyParams.version, ProtocolVersion.V004) >= 0) { /* keyParams.version >= 004 */ return true } else { - return !!Utils.isWebCryptoAvailable() || Utils.isReactNativeEnvironment() + return !!isWebCryptoAvailable() || isReactNativeEnvironment() } } - public supportedVersions(): Common.ProtocolVersion[] { - return [ - Common.ProtocolVersion.V001, - Common.ProtocolVersion.V002, - Common.ProtocolVersion.V003, - Common.ProtocolVersion.V004, - ] + public supportedVersions(): ProtocolVersion[] { + return [ProtocolVersion.V001, ProtocolVersion.V002, ProtocolVersion.V003, ProtocolVersion.V004] } /** * Determines whether the input version is greater than the latest supported library version. */ - public isVersionNewerThanLibraryVersion(version: Common.ProtocolVersion) { - const libraryVersion = Common.ProtocolVersionLatest - return Common.compareVersions(version, libraryVersion) === 1 + public isVersionNewerThanLibraryVersion(version: ProtocolVersion) { + const libraryVersion = ProtocolVersionLatest + return compareVersions(version, libraryVersion) === 1 } /** @@ -384,13 +403,13 @@ export class EncryptionService extends Services.AbstractService= 0) { + public costMinimumForVersion(version: ProtocolVersion) { + if (compareVersions(version, ProtocolVersion.V003) >= 0) { throw 'Cost minimums only apply to versions <= 002' } - if (version === Common.ProtocolVersion.V001) { + if (version === ProtocolVersion.V001) { return V001Algorithm.PbkdfMinCost - } else if (version === Common.ProtocolVersion.V002) { + } else if (version === ProtocolVersion.V002) { return V002Algorithm.PbkdfMinCost } else { throw `Invalid version for cost minimum: ${version}` @@ -411,8 +430,8 @@ export class EncryptionService extends Services.AbstractService)[] - > { + ): Promise)[]> { const result = await DecryptBackupFile(file, this, password) return result } @@ -431,7 +448,7 @@ export class EncryptionService extends Services.AbstractService CreateEncryptedBackupFileContextPayload(payload)) const data: BackupFile = { - version: Common.ProtocolVersionLatest, + version: ProtocolVersionLatest, items: ejected, } @@ -457,12 +474,10 @@ export class EncryptionService extends Services.AbstractService item.content_type !== Common.ContentType.ItemsKey, - ) + const payloads = this.payloadManager.nonDeletedItems.filter((item) => item.content_type !== ContentType.ItemsKey) const data: BackupFile = { - version: Common.ProtocolVersionLatest, + version: ProtocolVersionLatest, items: payloads .map((payload) => { if (isDecryptedPayload(payload)) { @@ -557,7 +572,7 @@ export class EncryptionService extends Services.AbstractService 0) { + if (compareVersions(rootKey.keyVersion, ProtocolVersionLastNonrootItemsKey) > 0) { /** Is >= 004, not needed */ return false } @@ -616,7 +631,7 @@ export class EncryptionService extends Services.AbstractService { + public async createNewDefaultItemsKey(): Promise { return this.rootKeyEncryption.createNewDefaultItemsKey() } @@ -625,11 +640,11 @@ export class EncryptionService extends Services.AbstractService key.keyVersion === userVersion) - if (Utils.isNullOrUndefined(accountVersionedKey)) { + if (isNullOrUndefined(accountVersionedKey)) { await this.rootKeyEncryption.createNewDefaultItemsKey() } diff --git a/packages/services/src/Domain/Encryption/EncryptionServiceEvent.ts b/packages/services/src/Domain/Encryption/EncryptionServiceEvent.ts new file mode 100644 index 000000000..00b2e43b3 --- /dev/null +++ b/packages/services/src/Domain/Encryption/EncryptionServiceEvent.ts @@ -0,0 +1,3 @@ +export enum EncryptionServiceEvent { + RootKeyStatusChanged = 'RootKeyStatusChanged', +} diff --git a/packages/encryption/src/Domain/Keys/Utils/DecryptItemsKey.ts b/packages/services/src/Domain/Encryption/Functions.ts similarity index 85% rename from packages/encryption/src/Domain/Keys/Utils/DecryptItemsKey.ts rename to packages/services/src/Domain/Encryption/Functions.ts index a8f5f2602..c3c5105f7 100644 --- a/packages/encryption/src/Domain/Keys/Utils/DecryptItemsKey.ts +++ b/packages/services/src/Domain/Encryption/Functions.ts @@ -5,15 +5,11 @@ import { ItemsKeyContent, RootKeyInterface, } from '@standardnotes/models' -import { - ChallengePrompt, - ChallengeReason, - ChallengeServiceInterface, - ChallengeValidation, -} from '@standardnotes/services' -import { EncryptionProvider } from '../../Service/Encryption/EncryptionProvider' -import { SNRootKeyParams } from '../RootKey/RootKeyParams' -import { KeyRecoveryStrings } from './KeyRecoveryStrings' +import { EncryptionProvider, KeyRecoveryStrings, SNRootKeyParams } from '@standardnotes/encryption' +import { ChallengeServiceInterface } from '../Challenge/ChallengeServiceInterface' +import { ChallengePrompt } from '../Challenge/Prompt/ChallengePrompt' +import { ChallengeReason } from '../Challenge/Types/ChallengeReason' +import { ChallengeValidation } from '../Challenge/Types/ChallengeValidation' export async function DecryptItemsKeyWithUserFallback( itemsKey: EncryptedPayloadInterface, diff --git a/packages/encryption/src/Domain/Service/Items/ItemsEncryption.ts b/packages/services/src/Domain/Encryption/ItemsEncryption.ts similarity index 67% rename from packages/encryption/src/Domain/Service/Items/ItemsEncryption.ts rename to packages/services/src/Domain/Encryption/ItemsEncryption.ts index c829c20f6..9e1f6a124 100644 --- a/packages/encryption/src/Domain/Service/Items/ItemsEncryption.ts +++ b/packages/services/src/Domain/Encryption/ItemsEncryption.ts @@ -1,30 +1,45 @@ import { ContentType, ProtocolVersion } from '@standardnotes/common' -import * as Models from '@standardnotes/models' -import { isEncryptedPayload } from '@standardnotes/models' -import * as Services from '@standardnotes/services' -import { DiagnosticInfo } from '@standardnotes/services' -import { Uuids } from '@standardnotes/utils' -import { OperatorManager } from '../../Operator/OperatorManager' -import * as OperatorWrapper from '../../Operator/OperatorWrapper' -import { StandardException } from '../../StandardException' import { DecryptedParameters, EncryptedParameters, ErrorDecryptingParameters, + findDefaultItemsKey, isErrorDecryptingParameters, -} from '../../Types/EncryptedParameters' -import { findDefaultItemsKey } from '../Functions' + OperatorManager, + StandardException, + encryptPayload, + decryptPayload, +} from '@standardnotes/encryption' +import { + DecryptedPayload, + DecryptedPayloadInterface, + EncryptedPayload, + EncryptedPayloadInterface, + isEncryptedPayload, + ItemContent, + ItemsKeyInterface, + PayloadEmitSource, + SureFindPayload, +} from '@standardnotes/models' +import { Uuids } from '@standardnotes/utils' -export class ItemsEncryptionService extends Services.AbstractService { +import { DiagnosticInfo } from '../Diagnostics/ServiceDiagnostics' +import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' +import { ItemManagerInterface } from '../Item/ItemManagerInterface' +import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface' +import { AbstractService } from '../Service/AbstractService' +import { StorageServiceInterface } from '../Storage/StorageServiceInterface' + +export class ItemsEncryptionService extends AbstractService { private removeItemsObserver!: () => void public userVersion?: ProtocolVersion constructor( - private itemManager: Services.ItemManagerInterface, - private payloadManager: Services.PayloadManagerInterface, - private storageService: Services.StorageServiceInterface, + private itemManager: ItemManagerInterface, + private payloadManager: PayloadManagerInterface, + private storageService: StorageServiceInterface, private operatorManager: OperatorManager, - protected override internalEventBus: Services.InternalEventBusInterface, + protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) @@ -59,19 +74,19 @@ export class ItemsEncryptionService extends Services.AbstractService { return this.itemManager.getDisplayableItemsKeys() } - public itemsKeyForPayload(payload: Models.EncryptedPayloadInterface): Models.ItemsKeyInterface | undefined { + public itemsKeyForPayload(payload: EncryptedPayloadInterface): ItemsKeyInterface | undefined { return this.getItemsKeys().find( (key) => key.uuid === payload.items_key_id || key.duplicateOf === payload.items_key_id, ) } - public getDefaultItemsKey(): Models.ItemsKeyInterface | undefined { + public getDefaultItemsKey(): ItemsKeyInterface | undefined { return findDefaultItemsKey(this.getItemsKeys()) } - private keyToUseForItemEncryption(): Models.ItemsKeyInterface | StandardException { + private keyToUseForItemEncryption(): ItemsKeyInterface | StandardException { const defaultKey = this.getDefaultItemsKey() - let result: Models.ItemsKeyInterface | undefined = undefined + let result: ItemsKeyInterface | undefined = undefined if (this.userVersion && this.userVersion !== defaultKey?.keyVersion) { /** @@ -92,9 +107,7 @@ export class ItemsEncryptionService extends Services.AbstractService { return result } - private keyToUseForDecryptionOfPayload( - payload: Models.EncryptedPayloadInterface, - ): Models.ItemsKeyInterface | undefined { + private keyToUseForDecryptionOfPayload(payload: EncryptedPayloadInterface): ItemsKeyInterface | undefined { if (payload.items_key_id) { const itemsKey = this.itemsKeyForPayload(payload) return itemsKey @@ -104,7 +117,7 @@ export class ItemsEncryptionService extends Services.AbstractService { return defaultKey } - public async encryptPayloadWithKeyLookup(payload: Models.DecryptedPayloadInterface): Promise { + public async encryptPayloadWithKeyLookup(payload: DecryptedPayloadInterface): Promise { const key = this.keyToUseForItemEncryption() if (key instanceof StandardException) { @@ -115,8 +128,8 @@ export class ItemsEncryptionService extends Services.AbstractService { } public async encryptPayload( - payload: Models.DecryptedPayloadInterface, - key: Models.ItemsKeyInterface, + payload: DecryptedPayloadInterface, + key: ItemsKeyInterface, ): Promise { if (isEncryptedPayload(payload)) { throw Error('Attempting to encrypt already encrypted payload.') @@ -128,24 +141,22 @@ export class ItemsEncryptionService extends Services.AbstractService { throw Error('Attempting to encrypt payload with no UuidGenerator.') } - return OperatorWrapper.encryptPayload(payload, key, this.operatorManager) + return encryptPayload(payload, key, this.operatorManager) } public async encryptPayloads( - payloads: Models.DecryptedPayloadInterface[], - key: Models.ItemsKeyInterface, + payloads: DecryptedPayloadInterface[], + key: ItemsKeyInterface, ): Promise { return Promise.all(payloads.map((payload) => this.encryptPayload(payload, key))) } - public async encryptPayloadsWithKeyLookup( - payloads: Models.DecryptedPayloadInterface[], - ): Promise { + public async encryptPayloadsWithKeyLookup(payloads: DecryptedPayloadInterface[]): Promise { return Promise.all(payloads.map((payload) => this.encryptPayloadWithKeyLookup(payload))) } - public async decryptPayloadWithKeyLookup( - payload: Models.EncryptedPayloadInterface, + public async decryptPayloadWithKeyLookup( + payload: EncryptedPayloadInterface, ): Promise | ErrorDecryptingParameters> { const key = this.keyToUseForDecryptionOfPayload(payload) @@ -160,9 +171,9 @@ export class ItemsEncryptionService extends Services.AbstractService { return this.decryptPayload(payload, key) } - public async decryptPayload( - payload: Models.EncryptedPayloadInterface, - key: Models.ItemsKeyInterface, + public async decryptPayload( + payload: EncryptedPayloadInterface, + key: ItemsKeyInterface, ): Promise | ErrorDecryptingParameters> { if (!payload.content) { return { @@ -171,18 +182,18 @@ export class ItemsEncryptionService extends Services.AbstractService { } } - return OperatorWrapper.decryptPayload(payload, key, this.operatorManager) + return decryptPayload(payload, key, this.operatorManager) } - public async decryptPayloadsWithKeyLookup( - payloads: Models.EncryptedPayloadInterface[], + public async decryptPayloadsWithKeyLookup( + payloads: EncryptedPayloadInterface[], ): Promise<(DecryptedParameters | ErrorDecryptingParameters)[]> { return Promise.all(payloads.map((payload) => this.decryptPayloadWithKeyLookup(payload))) } - public async decryptPayloads( - payloads: Models.EncryptedPayloadInterface[], - key: Models.ItemsKeyInterface, + public async decryptPayloads( + payloads: EncryptedPayloadInterface[], + key: ItemsKeyInterface, ): Promise<(DecryptedParameters | ErrorDecryptingParameters)[]> { return Promise.all(payloads.map((payload) => this.decryptPayload(payload, key))) } @@ -196,21 +207,21 @@ export class ItemsEncryptionService extends Services.AbstractService { const resultParams = await this.decryptPayloadsWithKeyLookup(payloads) const decryptedPayloads = resultParams.map((params) => { - const original = Models.SureFindPayload(payloads, params.uuid) + const original = SureFindPayload(payloads, params.uuid) if (isErrorDecryptingParameters(params)) { - return new Models.EncryptedPayload({ + return new EncryptedPayload({ ...original.ejected(), ...params, }) } else { - return new Models.DecryptedPayload({ + return new DecryptedPayload({ ...original.ejected(), ...params, }) } }) - await this.payloadManager.emitPayloads(decryptedPayloads, Models.PayloadEmitSource.LocalChanged) + await this.payloadManager.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged) } /** @@ -222,8 +233,8 @@ export class ItemsEncryptionService extends Services.AbstractService { */ public defaultItemsKeyForItemVersion( version: ProtocolVersion, - fromKeys?: Models.ItemsKeyInterface[], - ): Models.ItemsKeyInterface | undefined { + fromKeys?: ItemsKeyInterface[], + ): ItemsKeyInterface | undefined { /** Try to find one marked default first */ const searchKeys = fromKeys || this.getItemsKeys() const priorityKey = searchKeys.find((key) => { diff --git a/packages/encryption/src/Domain/Service/RootKey/RootKeyEncryption.ts b/packages/services/src/Domain/Encryption/RootKeyEncryption.ts similarity index 76% rename from packages/encryption/src/Domain/Service/RootKey/RootKeyEncryption.ts rename to packages/services/src/Domain/Encryption/RootKeyEncryption.ts index 9c6f82bf3..67e97f57e 100644 --- a/packages/encryption/src/Domain/Service/RootKey/RootKeyEncryption.ts +++ b/packages/services/src/Domain/Encryption/RootKeyEncryption.ts @@ -1,49 +1,71 @@ -import * as Common from '@standardnotes/common' -import * as Models from '@standardnotes/models' import { + ApplicationIdentifier, + ProtocolVersionLatest, + ProtocolVersion, + AnyKeyParamsContent, + KeyParamsOrigination, + compareVersions, + ProtocolVersionLastNonrootItemsKey, + ContentType, +} from '@standardnotes/common' +import { + RootKeyServiceEvent, + KeyMode, + SNRootKeyParams, + OperatorManager, + CreateNewRootKey, + CreateAnyKeyParams, + SNRootKey, + isErrorDecryptingParameters, + EncryptedParameters, + DecryptedParameters, + ErrorDecryptingParameters, + findDefaultItemsKey, + ItemsKeyMutator, + encryptPayload, + decryptPayload, +} from '@standardnotes/encryption' +import { + CreateDecryptedItemFromPayload, DecryptedPayload, + DecryptedPayloadInterface, + DecryptedTransferPayload, + EncryptedPayload, + EncryptedPayloadInterface, + EncryptedTransferPayload, FillItemContentSpecialized, + ItemContent, ItemsKeyContent, ItemsKeyContentSpecialized, + ItemsKeyInterface, NamespacedRootKeyInKeychain, PayloadTimestampDefaults, RootKeyContent, RootKeyInterface, } from '@standardnotes/models' -import * as Services from '@standardnotes/services' import { UuidGenerator } from '@standardnotes/utils' -import { ItemsKeyMutator } from '../../Keys/ItemsKey/ItemsKeyMutator' -import { CreateNewRootKey } from '../../Keys/RootKey/Functions' -import { CreateAnyKeyParams } from '../../Keys/RootKey/KeyParamsFunctions' -import { SNRootKey } from '../../Keys/RootKey/RootKey' -import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams' -import { OperatorManager } from '../../Operator/OperatorManager' -import * as OperatorWrapper from '../../Operator/OperatorWrapper' -import { - DecryptedParameters, - EncryptedParameters, - ErrorDecryptingParameters, - isErrorDecryptingParameters, -} from '../../Types/EncryptedParameters' -import { findDefaultItemsKey } from '../Functions' -import { KeyMode } from './KeyMode' -export enum RootKeyServiceEvent { - RootKeyStatusChanged = 'RootKeyStatusChanged', -} +import { DeviceInterface } from '../Device/DeviceInterface' +import { DiagnosticInfo } from '../Diagnostics/ServiceDiagnostics' +import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' +import { ItemManagerInterface } from '../Item/ItemManagerInterface' +import { AbstractService } from '../Service/AbstractService' +import { StorageKey } from '../Storage/StorageKeys' +import { StorageServiceInterface } from '../Storage/StorageServiceInterface' +import { StorageValueModes } from '../Storage/StorageTypes' -export class RootKeyEncryptionService extends Services.AbstractService { +export class RootKeyEncryptionService extends AbstractService { private rootKey?: RootKeyInterface public keyMode = KeyMode.RootKeyNone public memoizedRootKeyParams?: SNRootKeyParams constructor( - private itemManager: Services.ItemManagerInterface, + private itemManager: ItemManagerInterface, private operatorManager: OperatorManager, - public deviceInterface: Services.DeviceInterface, - private storageService: Services.StorageServiceInterface, - private identifier: Common.ApplicationIdentifier, - protected override internalEventBus: Services.InternalEventBusInterface, + public deviceInterface: DeviceInterface, + private storageService: StorageServiceInterface, + private identifier: ApplicationIdentifier, + protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) } @@ -89,7 +111,7 @@ export class RootKeyEncryptionService extends Services.AbstractService { + public async getEncryptionSourceVersion(): Promise { if (this.hasAccount()) { return this.getSureUserVersion() } else if (this.hasPasscode()) { @@ -129,12 +151,12 @@ export class RootKeyEncryptionService extends Services.AbstractService { const rawKeyParams = await this.storageService.getValue( - Services.StorageKey.RootKeyWrapperKeyParams, - Services.StorageValueModes.Nonwrapped, + StorageKey.RootKeyWrapperKeyParams, + StorageValueModes.Nonwrapped, ) if (!rawKeyParams) { return undefined } - return CreateAnyKeyParams(rawKeyParams as Common.AnyKeyParamsContent) + return CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent) } public async getSureRootKeyWrapperKeyParams() { @@ -213,8 +235,8 @@ export class RootKeyEncryptionService extends Services.AbstractService { - const rawKeyParams = await this.storageService.getValue( - Services.StorageKey.RootKeyParams, - Services.StorageValueModes.Nonwrapped, - ) + const rawKeyParams = await this.storageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped) if (!rawKeyParams) { return } - this.memoizedRootKeyParams = CreateAnyKeyParams(rawKeyParams as Common.AnyKeyParamsContent) + this.memoizedRootKeyParams = CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent) return this.memoizedRootKeyParams } @@ -289,25 +308,21 @@ export class RootKeyEncryptionService extends Services.AbstractService(payload, wrappingKey) + const payload = new EncryptedPayload(wrappedKey) + const decrypted = await this.decryptPayload(payload, wrappingKey) if (isErrorDecryptingParameters(decrypted)) { throw Error('Unable to decrypt root key with provided wrapping key.') @@ -362,9 +377,9 @@ export class RootKeyEncryptionService extends Services.AbstractService( - Services.StorageKey.WrappedRootKey, - Services.StorageValueModes.Nonwrapped, + return this.storageService.getValue( + StorageKey.WrappedRootKey, + StorageValueModes.Nonwrapped, ) } @@ -481,7 +490,7 @@ export class RootKeyEncryptionService extends Services.AbstractService { + public async encrypPayloadWithKeyLookup(payload: DecryptedPayloadInterface): Promise { const key = this.getRootKey() if (key == undefined) { @@ -491,25 +500,20 @@ export class RootKeyEncryptionService extends Services.AbstractService { + public async encryptPayloadsWithKeyLookup(payloads: DecryptedPayloadInterface[]): Promise { return Promise.all(payloads.map((payload) => this.encrypPayloadWithKeyLookup(payload))) } - public async encryptPayload( - payload: Models.DecryptedPayloadInterface, - key: RootKeyInterface, - ): Promise { - return OperatorWrapper.encryptPayload(payload, key, this.operatorManager) + public async encryptPayload(payload: DecryptedPayloadInterface, key: RootKeyInterface): Promise { + return encryptPayload(payload, key, this.operatorManager) } - public async encryptPayloads(payloads: Models.DecryptedPayloadInterface[], key: RootKeyInterface) { + public async encryptPayloads(payloads: DecryptedPayloadInterface[], key: RootKeyInterface) { return Promise.all(payloads.map((payload) => this.encryptPayload(payload, key))) } - public async decryptPayloadWithKeyLookup( - payload: Models.EncryptedPayloadInterface, + public async decryptPayloadWithKeyLookup( + payload: EncryptedPayloadInterface, ): Promise | ErrorDecryptingParameters> { const key = this.getRootKey() @@ -524,21 +528,21 @@ export class RootKeyEncryptionService extends Services.AbstractService( - payload: Models.EncryptedPayloadInterface, + public async decryptPayload( + payload: EncryptedPayloadInterface, key: RootKeyInterface, ): Promise | ErrorDecryptingParameters> { - return OperatorWrapper.decryptPayload(payload, key, this.operatorManager) + return decryptPayload(payload, key, this.operatorManager) } - public async decryptPayloadsWithKeyLookup( - payloads: Models.EncryptedPayloadInterface[], + public async decryptPayloadsWithKeyLookup( + payloads: EncryptedPayloadInterface[], ): Promise<(DecryptedParameters | ErrorDecryptingParameters)[]> { return Promise.all(payloads.map((payload) => this.decryptPayloadWithKeyLookup(payload))) } - public async decryptPayloads( - payloads: Models.EncryptedPayloadInterface[], + public async decryptPayloads( + payloads: EncryptedPayloadInterface[], key: RootKeyInterface, ): Promise<(DecryptedParameters | ErrorDecryptingParameters)[]> { return Promise.all(payloads.map((payload) => this.decryptPayload(payload, key))) @@ -566,24 +570,24 @@ export class RootKeyEncryptionService extends Services.AbstractService { + public async createNewDefaultItemsKey(): Promise { const rootKey = this.getSureRootKey() - const operatorVersion = rootKey ? rootKey.keyVersion : Common.ProtocolVersionLatest - let itemTemplate: Models.ItemsKeyInterface + const operatorVersion = rootKey ? rootKey.keyVersion : ProtocolVersionLatest + let itemTemplate: ItemsKeyInterface - if (Common.compareVersions(operatorVersion, Common.ProtocolVersionLastNonrootItemsKey) <= 0) { + if (compareVersions(operatorVersion, ProtocolVersionLastNonrootItemsKey) <= 0) { /** Create root key based items key */ const payload = new DecryptedPayload({ uuid: UuidGenerator.GenerateUuid(), - content_type: Common.ContentType.ItemsKey, - content: Models.FillItemContentSpecialized({ + content_type: ContentType.ItemsKey, + content: FillItemContentSpecialized({ itemsKey: rootKey.masterKey, dataAuthenticationKey: rootKey.dataAuthenticationKey, version: operatorVersion, }), ...PayloadTimestampDefaults(), }) - itemTemplate = Models.CreateDecryptedItemFromPayload(payload) + itemTemplate = CreateDecryptedItemFromPayload(payload) } else { /** Create independent items key */ itemTemplate = this.operatorManager.operatorForVersion(operatorVersion).createItemsKey() @@ -600,7 +604,7 @@ export class RootKeyEncryptionService extends Services.AbstractService { mutator.isDefault = true @@ -626,7 +630,7 @@ export class RootKeyEncryptionService extends Services.AbstractService { + override async getDiagnostics(): Promise { return { rootKeyEncryption: { hasRootKey: this.rootKey != undefined, diff --git a/packages/services/src/Domain/Files/FileService.ts b/packages/services/src/Domain/Files/FileService.ts index 672237090..e7320ecf7 100644 --- a/packages/services/src/Domain/Files/FileService.ts +++ b/packages/services/src/Domain/Files/FileService.ts @@ -1,4 +1,3 @@ -import { DecryptedBytes, EncryptedBytes, FileMemoryCache, OrderedByteChunker } from '@standardnotes/filepicker' import { ClientDisplayableError } from '@standardnotes/responses' import { ContentType } from '@standardnotes/common' import { @@ -13,21 +12,7 @@ import { } from '@standardnotes/models' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { UuidGenerator } from '@standardnotes/utils' -import { - AbstractService, - InternalEventBusInterface, - ItemManagerInterface, - SyncServiceInterface, - AlertService, - FileSystemApi, - FilesApiInterface, - FileBackupMetadataFile, - FileHandleRead, - FileSystemNoSelection, - ChallengeServiceInterface, - FileBackupsConstantsV1, -} from '@standardnotes/services' -import { DecryptItemsKeyWithUserFallback, EncryptionProvider, SNItemsKey } from '@standardnotes/encryption' +import { EncryptionProvider, SNItemsKey } from '@standardnotes/encryption' import { DownloadAndDecryptFileOperation, EncryptAndUploadFileOperation, @@ -35,8 +20,26 @@ import { FileDownloadProgress, FilesClientInterface, readAndDecryptBackupFile, + FilesApiInterface, + FileBackupsConstantsV1, + FileBackupMetadataFile, + FileSystemApi, + FileHandleRead, + FileSystemNoSelection, + EncryptedBytes, + DecryptedBytes, + OrderedByteChunker, + FileMemoryCache, } from '@standardnotes/files' +import { AlertService } from '../Alert/AlertService' +import { ChallengeServiceInterface } from '../Challenge' +import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' +import { ItemManagerInterface } from '../Item/ItemManagerInterface' +import { AbstractService } from '../Service/AbstractService' +import { SyncServiceInterface } from '../Sync/SyncServiceInterface' +import { DecryptItemsKeyWithUserFallback } from '../Encryption/Functions' + const OneHundredMb = 100 * 1_000_000 export class FileService extends AbstractService implements FilesClientInterface { diff --git a/packages/services/src/Domain/index.ts b/packages/services/src/Domain/index.ts index 103656712..856c2656a 100644 --- a/packages/services/src/Domain/index.ts +++ b/packages/services/src/Domain/index.ts @@ -18,11 +18,16 @@ export * from './Device/DesktopManagerInterface' export * from './Device/DesktopWebCommunication' export * from './Device/DeviceInterface' export * from './Device/Environments' -export * from './Device/FileBackupsDevice' export * from './Device/MobileDeviceInterface' export * from './Device/TypeCheck' export * from './Device/WebOrDesktopDeviceInterface' export * from './Diagnostics/ServiceDiagnostics' +export * from './Encryption/BackupFileDecryptor' +export * from './Encryption/EncryptionService' +export * from './Encryption/EncryptionServiceEvent' +export * from './Encryption/Functions' +export * from './Encryption/ItemsEncryption' +export * from './Encryption/RootKeyEncryption' export * from './Event/ApplicationEvent' export * from './Event/ApplicationEventCallback' export * from './Event/EventObserver' @@ -34,6 +39,7 @@ export * from './Feature/FeaturesClientInterface' export * from './Feature/FeaturesEvent' export * from './Feature/OfflineSubscriptionEntitlements' export * from './Feature/SetOfflineFeaturesFunctionResponse' +export * from './Files/FileService' export * from './Integrity/IntegrityApiInterface' export * from './Integrity/IntegrityEvent' export * from './Integrity/IntegrityEventPayload' diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 1f438f957..c1f446999 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -1,4 +1,3 @@ -import { SnjsVersion } from './../Version' import { HttpService, HttpServiceInterface, @@ -10,13 +9,11 @@ import { } from '@standardnotes/api' import * as Common from '@standardnotes/common' import * as ExternalServices from '@standardnotes/services' -import * as Encryption from '@standardnotes/encryption' import * as Models from '@standardnotes/models' import * as Responses from '@standardnotes/responses' import * as InternalServices from '../Services' import * as Utils from '@standardnotes/utils' import * as Settings from '@standardnotes/settings' -import * as Files from '@standardnotes/files' import { Subscription } from '@standardnotes/security' import { UuidString, ApplicationEventPayload } from '../Types' import { applicationEventForSyncEvent } from '@Lib/Application/Event' @@ -36,11 +33,19 @@ import { DeinitSource, AppGroupManagedApplication, ApplicationInterface, + EncryptionService, + EncryptionServiceEvent, + FilesBackupService, + FileService, } from '@standardnotes/services' -import { SNLog } from '../Log' +import { FilesClientInterface } from '@standardnotes/files' +import { ComputePrivateWorkspaceIdentifier } from '@standardnotes/encryption' import { useBoolean } from '@standardnotes/utils' import { BackupFile, DecryptedItemInterface, EncryptedItemInterface, ItemStream } from '@standardnotes/models' import { ClientDisplayableError } from '@standardnotes/responses' + +import { SnjsVersion } from './../Version' +import { SNLog } from '../Log' import { Challenge, ChallengeResponse } from '../Services' import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions' import { ApplicationOptionsDefaults } from './Options/Defaults' @@ -78,7 +83,7 @@ export class SNApplication private deprecatedHttpService!: InternalServices.SNHttpService private declare httpService: HttpServiceInterface private payloadManager!: InternalServices.PayloadManager - public protocolService!: Encryption.EncryptionService + public protocolService!: EncryptionService private diskStorageService!: InternalServices.DiskStorageService private inMemoryStore!: ExternalServices.KeyValueStoreInterface /** @@ -104,11 +109,11 @@ export class SNApplication private settingsService!: InternalServices.SNSettingsService private mfaService!: InternalServices.SNMfaService private listedService!: InternalServices.ListedService - private fileService!: Files.FileService + private fileService!: FileService private mutatorService!: InternalServices.MutatorService private integrityService!: ExternalServices.IntegrityService private statusService!: ExternalServices.StatusService - private filesBackupService?: Files.FilesBackupService + private filesBackupService?: FilesBackupService private internalEventBus!: ExternalServices.InternalEventBusInterface @@ -182,7 +187,7 @@ export class SNApplication this.defineInternalEventHandlers() } - public get files(): Files.FilesClientInterface { + public get files(): FilesClientInterface { return this.fileService } @@ -222,7 +227,7 @@ export class SNApplication return this.statusService } - public get fileBackups(): Files.FilesBackupService | undefined { + public get fileBackups(): FilesBackupService | undefined { return this.filesBackupService } @@ -231,7 +236,7 @@ export class SNApplication } public computePrivateWorkspaceIdentifier(userphrase: string, name: string): Promise { - return Encryption.ComputePrivateWorkspaceIdentifier(this.options.crypto, userphrase, name) + return ComputePrivateWorkspaceIdentifier(this.options.crypto, userphrase, name) } /** @@ -1117,7 +1122,7 @@ export class SNApplication } private createFileService() { - this.fileService = new Files.FileService( + this.fileService = new FileService( this.apiService, this.itemManager, this.syncService, @@ -1328,7 +1333,7 @@ export class SNApplication } private createProtocolService() { - this.protocolService = new Encryption.EncryptionService( + this.protocolService = new EncryptionService( this.itemManager, this.payloadManager, this.deviceInterface, @@ -1339,7 +1344,7 @@ export class SNApplication ) this.serviceObservers.push( this.protocolService.addEventObserver(async (event) => { - if (event === Encryption.EncryptionServiceEvent.RootKeyStatusChanged) { + if (event === EncryptionServiceEvent.RootKeyStatusChanged) { await this.notifyEvent(ApplicationEvent.KeyStatusChanged) } }), @@ -1539,7 +1544,7 @@ export class SNApplication } private createFilesBackupService(device: ExternalServices.DesktopDeviceInterface): void { - this.filesBackupService = new Files.FilesBackupService( + this.filesBackupService = new FilesBackupService( this.itemManager, this.apiService, this.protocolService, diff --git a/packages/snjs/lib/Migrations/MigrationServices.ts b/packages/snjs/lib/Migrations/MigrationServices.ts index 404425f6c..0185cd4f3 100644 --- a/packages/snjs/lib/Migrations/MigrationServices.ts +++ b/packages/snjs/lib/Migrations/MigrationServices.ts @@ -1,8 +1,7 @@ import { SNSessionManager } from '../Services/Session/SessionManager' import { ApplicationIdentifier } from '@standardnotes/common' import { ItemManager } from '@Lib/Services/Items/ItemManager' -import { EncryptionService } from '@standardnotes/encryption' -import { DeviceInterface, InternalEventBusInterface, Environment } from '@standardnotes/services' +import { DeviceInterface, InternalEventBusInterface, Environment, EncryptionService } from '@standardnotes/services' import { ChallengeService, SNSingletonManager, SNFeaturesService, DiskStorageService } from '@Lib/Services' export type MigrationServices = { diff --git a/packages/snjs/lib/Services/Actions/ActionsService.ts b/packages/snjs/lib/Services/Actions/ActionsService.ts index 88a2c82f8..1fd006cc3 100644 --- a/packages/snjs/lib/Services/Actions/ActionsService.ts +++ b/packages/snjs/lib/Services/Actions/ActionsService.ts @@ -1,4 +1,4 @@ -import { EncryptionService, SNRootKey } from '@standardnotes/encryption' +import { SNRootKey } from '@standardnotes/encryption' import { Challenge, ChallengeService } from '../Challenge' import { ListedService } from '../Listed/ListedService' import { ActionResponse, HttpResponse } from '@standardnotes/responses' @@ -30,6 +30,7 @@ import { ChallengeValidation, ChallengeReason, ChallengePrompt, + EncryptionService, } from '@standardnotes/services' /** diff --git a/packages/snjs/lib/Services/Api/ApiService.ts b/packages/snjs/lib/Services/Api/ApiService.ts index 9200daa94..6277c2bf8 100644 --- a/packages/snjs/lib/Services/Api/ApiService.ts +++ b/packages/snjs/lib/Services/Api/ApiService.ts @@ -12,9 +12,10 @@ import { ApiServiceEvent, MetaReceivedData, DiagnosticInfo, - FilesApiInterface, KeyValueStoreInterface, } from '@standardnotes/services' +import { FilesApiInterface } from '@standardnotes/files' + import { ServerSyncPushContextualPayload, SNFeatureRepo, FileContent } from '@standardnotes/models' import * as Responses from '@standardnotes/responses' import { API_MESSAGE_FAILED_OFFLINE_ACTIVATION } from '@Lib/Services/Api/Messages' diff --git a/packages/snjs/lib/Services/Challenge/ChallengeService.ts b/packages/snjs/lib/Services/Challenge/ChallengeService.ts index 7783143b2..3f6729de6 100644 --- a/packages/snjs/lib/Services/Challenge/ChallengeService.ts +++ b/packages/snjs/lib/Services/Challenge/ChallengeService.ts @@ -1,5 +1,4 @@ import { RootKeyInterface } from '@standardnotes/models' -import { EncryptionService } from '@standardnotes/encryption' import { DiskStorageService } from '../Storage/DiskStorageService' import { removeFromArray } from '@standardnotes/utils' import { isValidProtectionSessionLength } from '../Protection/ProtectionService' @@ -14,6 +13,7 @@ import { ChallengeInterface, ChallengePromptInterface, ChallengePrompt, + EncryptionService, } from '@standardnotes/services' import { ChallengeResponse } from './ChallengeResponse' import { ChallengeOperation } from './ChallengeOperation' diff --git a/packages/snjs/lib/Services/History/HistoryManager.ts b/packages/snjs/lib/Services/History/HistoryManager.ts index d59ff37c4..97d7e9623 100644 --- a/packages/snjs/lib/Services/History/HistoryManager.ts +++ b/packages/snjs/lib/Services/History/HistoryManager.ts @@ -1,5 +1,4 @@ import { ContentType, Uuid } from '@standardnotes/common' -import { EncryptionService } from '@standardnotes/encryption' import { isNullOrUndefined, removeFromArray } from '@standardnotes/utils' import { ItemManager } from '@Lib/Services/Items/ItemManager' import { SNApiService } from '@Lib/Services/Api/ApiService' @@ -7,8 +6,8 @@ import { DiskStorageService } from '@Lib/Services/Storage/DiskStorageService' import { UuidString } from '../../Types/UuidString' import * as Models from '@standardnotes/models' import * as Responses from '@standardnotes/responses' -import * as Services from '@standardnotes/services' import { isErrorDecryptingPayload, PayloadTimestampDefaults, SNNote } from '@standardnotes/models' +import { AbstractService, EncryptionService, DeviceInterface, InternalEventBusInterface } from '@standardnotes/services' /** The amount of revisions per item above which should call for an optimization. */ const DefaultItemRevisionsThreshold = 20 @@ -28,7 +27,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 Services.AbstractService { +export class SNHistoryManager extends AbstractService { private removeChangeObserver: () => void /** @@ -49,8 +48,8 @@ export class SNHistoryManager extends Services.AbstractService { private storageService: DiskStorageService, private apiService: SNApiService, private protocolService: EncryptionService, - public deviceInterface: Services.DeviceInterface, - protected override internalEventBus: Services.InternalEventBusInterface, + public deviceInterface: DeviceInterface, + protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) this.removeChangeObserver = this.itemManager.addObserver(ContentType.Note, ({ changed, inserted }) => { diff --git a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryOperation.ts b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryOperation.ts index ffc7b18ad..ec733c4b7 100644 --- a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryOperation.ts +++ b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryOperation.ts @@ -1,10 +1,10 @@ import { ContentType } from '@standardnotes/common' import { ItemsKeyInterface } from '@standardnotes/models' import { dateSorted } from '@standardnotes/utils' -import { SNRootKeyParams, DecryptItemsKeyByPromptingUser, EncryptionProvider } from '@standardnotes/encryption' +import { SNRootKeyParams, EncryptionProvider } from '@standardnotes/encryption' import { DecryptionQueueItem, KeyRecoveryOperationResult } from './Types' import { serverKeyParamsAreSafe } from './Utils' -import { ChallengeServiceInterface } from '@standardnotes/services' +import { ChallengeServiceInterface, DecryptItemsKeyByPromptingUser } from '@standardnotes/services' import { ItemManager } from '../Items' export class KeyRecoveryOperation { diff --git a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts index 3a3a5a385..b8f8b3d35 100644 --- a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts +++ b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts @@ -1,11 +1,5 @@ import { KeyRecoveryOperation } from './KeyRecoveryOperation' -import { - SNRootKeyParams, - EncryptionService, - SNRootKey, - KeyParamsFromApiResponse, - KeyRecoveryStrings, -} from '@standardnotes/encryption' +import { SNRootKeyParams, SNRootKey, KeyParamsFromApiResponse, KeyRecoveryStrings } from '@standardnotes/encryption' import { UserService } from '../User/UserService' import { isErrorDecryptingPayload, @@ -37,6 +31,7 @@ import { ChallengeValidation, ChallengeReason, ChallengePrompt, + EncryptionService, } from '@standardnotes/services' import { UndecryptableItemsStorage, diff --git a/packages/snjs/lib/Services/Mutator/MutatorService.spec.ts b/packages/snjs/lib/Services/Mutator/MutatorService.spec.ts index 1d21c0063..7d9d4fcfe 100644 --- a/packages/snjs/lib/Services/Mutator/MutatorService.spec.ts +++ b/packages/snjs/lib/Services/Mutator/MutatorService.spec.ts @@ -1,8 +1,7 @@ import { SNHistoryManager } from './../History/HistoryManager' import { NoteContent, SNNote, FillItemContent, DecryptedPayload, PayloadTimestampDefaults } from '@standardnotes/models' -import { EncryptionService } from '@standardnotes/encryption' import { ContentType } from '@standardnotes/common' -import { InternalEventBusInterface } from '@standardnotes/services' +import { EncryptionService, InternalEventBusInterface } from '@standardnotes/services' import { ChallengeService, MutatorService, diff --git a/packages/snjs/lib/Services/Protection/ProtectionService.spec.ts b/packages/snjs/lib/Services/Protection/ProtectionService.spec.ts index 1093ccc20..fa4eecae6 100644 --- a/packages/snjs/lib/Services/Protection/ProtectionService.spec.ts +++ b/packages/snjs/lib/Services/Protection/ProtectionService.spec.ts @@ -1,8 +1,12 @@ import { ChallengeService } from '../Challenge' -import { EncryptionService } from '@standardnotes/encryption' import { DiskStorageService } from '../Storage/DiskStorageService' import { SNProtectionService } from './ProtectionService' -import { InternalEventBus, InternalEventBusInterface, ChallengeReason } from '@standardnotes/services' +import { + InternalEventBus, + InternalEventBusInterface, + ChallengeReason, + EncryptionService, +} from '@standardnotes/services' import { UuidGenerator } from '@standardnotes/utils' import { DecryptedPayload, diff --git a/packages/snjs/lib/Services/Protection/ProtectionService.ts b/packages/snjs/lib/Services/Protection/ProtectionService.ts index c7aef9f8f..17fb3ee6d 100644 --- a/packages/snjs/lib/Services/Protection/ProtectionService.ts +++ b/packages/snjs/lib/Services/Protection/ProtectionService.ts @@ -2,7 +2,6 @@ import { Challenge } from './../Challenge/Challenge' import { ChallengeService } from './../Challenge/ChallengeService' import { SNLog } from '@Lib/Log' import { DecryptedItem } from '@standardnotes/models' -import { EncryptionService } from '@standardnotes/encryption' import { DiskStorageService } from '@Lib/Services/Storage/DiskStorageService' import { isNullOrUndefined } from '@standardnotes/utils' import { @@ -15,6 +14,7 @@ import { ChallengeReason, ChallengePrompt, ChallengeValidation, + EncryptionService, } from '@standardnotes/services' import { ProtectionsClientInterface } from './ClientInterface' import { ContentType } from '@standardnotes/common' diff --git a/packages/snjs/lib/Services/Session/SessionManager.ts b/packages/snjs/lib/Services/Session/SessionManager.ts index 056bfda0b..4b6202819 100644 --- a/packages/snjs/lib/Services/Session/SessionManager.ts +++ b/packages/snjs/lib/Services/Session/SessionManager.ts @@ -9,19 +9,14 @@ import { ChallengeKeyboardType, ChallengeReason, ChallengePromptTitle, + EncryptionService, } from '@standardnotes/services' import { Base64String } from '@standardnotes/sncrypto-common' import { ClientDisplayableError } from '@standardnotes/responses' import { CopyPayloadWithContentOverride } from '@standardnotes/models' import { isNullOrUndefined } from '@standardnotes/utils' import { JwtSession } from './Sessions/JwtSession' -import { - KeyParamsFromApiResponse, - SNRootKeyParams, - SNRootKey, - EncryptionService, - CreateNewRootKey, -} from '@standardnotes/encryption' +import { KeyParamsFromApiResponse, SNRootKeyParams, SNRootKey, CreateNewRootKey } from '@standardnotes/encryption' import { SessionStrings, SignInStrings } from '../Api/Messages' import { RemoteSession, RawStorageValue } from './Sessions/Types' import { Session } from './Sessions/Session' diff --git a/packages/snjs/lib/Services/Sync/SyncService.ts b/packages/snjs/lib/Services/Sync/SyncService.ts index 12a27a62a..76080516a 100644 --- a/packages/snjs/lib/Services/Sync/SyncService.ts +++ b/packages/snjs/lib/Services/Sync/SyncService.ts @@ -26,7 +26,6 @@ import { ServerSyncResponse } from '@Lib/Services/Sync/Account/Response' import { ServerSyncResponseResolver } from '@Lib/Services/Sync/Account/ResponseResolver' import { SyncSignal, SyncStats } from '@Lib/Services/Sync/Signals' import { UuidString } from '../../Types/UuidString' -import * as Encryption from '@standardnotes/encryption' import { PayloadSource, CreateDecryptedItemFromPayload, @@ -73,9 +72,15 @@ import { SyncQueueStrategy, SyncServiceInterface, DiagnosticInfo, + EncryptionService, } from '@standardnotes/services' import { OfflineSyncResponse } from './Offline/Response' -import { KeyedDecryptionSplit, SplitPayloadsByEncryptionType } from '@standardnotes/encryption' +import { + CreateDecryptionSplitWithKeyLookup, + CreateEncryptionSplitWithKeyLookup, + KeyedDecryptionSplit, + SplitPayloadsByEncryptionType, +} from '@standardnotes/encryption' import { CreatePayloadFromRawServerItem } from './Account/Utilities' import { ApplicationSyncOptions } from '@Lib/Application/Options/OptionalOptions' @@ -131,7 +136,7 @@ export class SNSyncService constructor( private itemManager: ItemManager, private sessionManager: SNSessionManager, - private protocolService: Encryption.EncryptionService, + private protocolService: EncryptionService, private storageService: DiskStorageService, private payloadManager: PayloadManager, private apiService: SNApiService, @@ -226,7 +231,7 @@ export class SNSyncService isDecryptedPayload, ) as DecryptedPayloadInterface[] - const itemsKeysSplit: Encryption.KeyedDecryptionSplit = { + const itemsKeysSplit: KeyedDecryptionSplit = { usesRootKeyWithKeyLookup: { items: encryptedItemsKeysPayloads, }, @@ -301,7 +306,7 @@ export class SNSyncService } } - const split: Encryption.KeyedDecryptionSplit = { + const split: KeyedDecryptionSplit = { usesItemsKeyWithKeyLookup: { items: encrypted, }, @@ -440,9 +445,9 @@ export class SNSyncService ): Promise { const payloadSplit = CreatePayloadSplit(payloads) - const encryptionSplit = Encryption.SplitPayloadsByEncryptionType(payloadSplit.decrypted) + const encryptionSplit = SplitPayloadsByEncryptionType(payloadSplit.decrypted) - const keyLookupSplit = Encryption.CreateEncryptionSplitWithKeyLookup(encryptionSplit) + const keyLookupSplit = CreateEncryptionSplitWithKeyLookup(encryptionSplit) const encryptedResults = await this.protocolService.encryptSplit(keyLookupSplit) @@ -973,7 +978,7 @@ export class SNSyncService ? (CreateDecryptedItemFromPayload(previouslyProcessedItemsKey) as ItemsKeyInterface) : undefined - const keyedSplit: Encryption.KeyedDecryptionSplit = {} + const keyedSplit: KeyedDecryptionSplit = {} if (itemsKey) { keyedSplit.usesItemsKey = { items: [encrypted], @@ -1162,9 +1167,9 @@ export class SNSyncService const payloadSplit = CreateNonDecryptedPayloadSplit(receivedPayloads) - const encryptionSplit = Encryption.SplitPayloadsByEncryptionType(payloadSplit.encrypted) + const encryptionSplit = SplitPayloadsByEncryptionType(payloadSplit.encrypted) - const keyedSplit = Encryption.CreateDecryptionSplitWithKeyLookup(encryptionSplit) + const keyedSplit = CreateDecryptionSplitWithKeyLookup(encryptionSplit) const decryptionResults = await this.protocolService.decryptSplit(keyedSplit) diff --git a/packages/snjs/lib/Services/User/UserService.ts b/packages/snjs/lib/Services/User/UserService.ts index 9cca8bd6a..3d7f2decc 100644 --- a/packages/snjs/lib/Services/User/UserService.ts +++ b/packages/snjs/lib/Services/User/UserService.ts @@ -1,6 +1,6 @@ import { Challenge } from '../Challenge' import { ChallengeService } from '../Challenge/ChallengeService' -import { EncryptionService, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption' +import { SNRootKey, SNRootKeyParams } from '@standardnotes/encryption' import { HttpResponse, SignInResponse, User } from '@standardnotes/responses' import { ItemManager } from '@Lib/Services/Items/ItemManager' import { KeyParamsOrigination } from '@standardnotes/common' @@ -14,6 +14,7 @@ import { InternalEventBusInterface, UserClientInterface, StoragePersistencePolicies, + EncryptionService, } from '@standardnotes/services' import { SNApiService } from './../Api/ApiService' import { SNProtectionService } from '../Protection/ProtectionService' diff --git a/packages/web/src/javascripts/Theme/ThemeManager.ts b/packages/ui-services/src/Theme/ThemeManager.ts similarity index 88% rename from packages/web/src/javascripts/Theme/ThemeManager.ts rename to packages/ui-services/src/Theme/ThemeManager.ts index 52aaf9ac5..8f623b0cb 100644 --- a/packages/web/src/javascripts/Theme/ThemeManager.ts +++ b/packages/ui-services/src/Theme/ThemeManager.ts @@ -8,31 +8,40 @@ import { SNTheme, } from '@standardnotes/models' import { removeFromArray } from '@standardnotes/utils' -import { ApplicationEvent, ApplicationService, FeatureStatus, InternalEventBus, StorageValueModes, WebApplicationInterface } from '@standardnotes/snjs' +import { + AbstractService, + WebApplicationInterface, + InternalEventBusInterface, + ApplicationEvent, + StorageValueModes, + FeatureStatus, +} from '@standardnotes/services' const CachedThemesKey = 'cachedThemes' const TimeBeforeApplyingColorScheme = 5 const DefaultThemeIdentifier = 'Default' -export class ThemeManager extends ApplicationService { +export class ThemeManager extends AbstractService { private activeThemes: Uuid[] = [] private unregisterDesktop?: () => void private unregisterStream!: () => void private lastUseDeviceThemeSettings = false + private unsubApp!: () => void - constructor(application: WebApplicationInterface) { - super(application, new InternalEventBus()) + constructor( + protected application: WebApplicationInterface, + protected override internalEventBus: InternalEventBusInterface, + ) { + super(internalEventBus) + this.addAppEventObserverAfterSubclassesFinishConstructing() this.colorSchemeEventHandler = this.colorSchemeEventHandler.bind(this) } - override async onAppStart() { - super.onAppStart().catch(console.error) + async onAppStart() { this.registerObservers() } - override async onAppEvent(event: ApplicationEvent) { - super.onAppEvent(event).catch(console.error) - + async onAppEvent(event: ApplicationEvent) { switch (event) { case ApplicationEvent.SignedOut: { this.deactivateAllThemes() @@ -59,6 +68,25 @@ export class ThemeManager extends ApplicationService { } } + addAppEventObserverAfterSubclassesFinishConstructing() { + setTimeout(() => { + this.addAppEventObserver() + }, 0) + } + + addAppEventObserver() { + if (this.application.isStarted()) { + void this.onAppStart() + } + + this.unsubApp = this.application.addEventObserver(async (event: ApplicationEvent) => { + await this.onAppEvent(event) + if (event === ApplicationEvent.Started) { + void this.onAppStart() + } + }) + } + private handlePreferencesChangeEvent(): void { const useDeviceThemeSettings = this.application.getPreference(PrefKey.UseSystemColorScheme, false) @@ -86,6 +114,10 @@ export class ThemeManager extends ApplicationService { ;(this.unregisterStream as unknown) = undefined window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.colorSchemeEventHandler) + ;(this.application as unknown) = undefined + + this.unsubApp() + ;(this.unsubApp as unknown) = undefined super.deinit() } diff --git a/packages/ui-services/src/index.ts b/packages/ui-services/src/index.ts index 4c41fd80f..10a482bde 100644 --- a/packages/ui-services/src/index.ts +++ b/packages/ui-services/src/index.ts @@ -4,3 +4,4 @@ export * from './Archive/ArchiveManager' export * from './IO/IOService' export * from './Security/AutolockService' export * from './Storage/LocalStorage' +export * from './Theme/ThemeManager' diff --git a/packages/web/src/javascripts/Application/Application.ts b/packages/web/src/javascripts/Application/Application.ts index 30b47c538..6960c2479 100644 --- a/packages/web/src/javascripts/Application/Application.ts +++ b/packages/web/src/javascripts/Application/Application.ts @@ -22,8 +22,7 @@ import { makeObservable, observable } from 'mobx' import { PanelResizedData } from '@/Types/PanelResizedData' import { isDesktopApplication } from '@/Utils' import { DesktopManager } from './Device/DesktopManager' -import { ArchiveManager, AutolockService, IOService, WebAlertService } from '@standardnotes/ui-services' -import { ThemeManager } from '@/Theme/ThemeManager' +import { ArchiveManager, AutolockService, IOService, WebAlertService, ThemeManager } from '@standardnotes/ui-services' type WebServices = { viewControllerManager: ViewControllerManager diff --git a/packages/web/src/javascripts/Application/ApplicationGroup.ts b/packages/web/src/javascripts/Application/ApplicationGroup.ts index c21569ed1..27ec2daa3 100644 --- a/packages/web/src/javascripts/Application/ApplicationGroup.ts +++ b/packages/web/src/javascripts/Application/ApplicationGroup.ts @@ -33,8 +33,9 @@ const createApplication = ( const viewControllerManager = new ViewControllerManager(application, device) const archiveService = new ArchiveManager(application) const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop) - const autolockService = new AutolockService(application, new InternalEventBus()) - const themeService = new ThemeManager(application) + const internalEventBus = new InternalEventBus() + const autolockService = new AutolockService(application, internalEventBus) + const themeService = new ThemeManager(application, internalEventBus) application.setWebServices({ viewControllerManager, diff --git a/yarn.lock b/yarn.lock index f5f7d31f9..e9d3e3b59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6576,7 +6576,7 @@ __metadata: languageName: node linkType: hard -"@standardnotes/encryption@workspace:*, @standardnotes/encryption@workspace:packages/encryption": +"@standardnotes/encryption@workspace:*, @standardnotes/encryption@workspace:^, @standardnotes/encryption@workspace:packages/encryption": version: 0.0.0-use.local resolution: "@standardnotes/encryption@workspace:packages/encryption" dependencies: @@ -6584,7 +6584,6 @@ __metadata: "@standardnotes/config": 2.4.3 "@standardnotes/models": "workspace:*" "@standardnotes/responses": "workspace:*" - "@standardnotes/services": "workspace:*" "@standardnotes/sncrypto-common": "workspace:*" "@standardnotes/utils": "workspace:*" "@types/jest": ^28.1.5 @@ -7179,6 +7178,7 @@ __metadata: dependencies: "@standardnotes/auth": ^3.19.4 "@standardnotes/common": ^1.30.0 + "@standardnotes/encryption": "workspace:^" "@standardnotes/files": "workspace:^" "@standardnotes/models": "workspace:^" "@standardnotes/responses": "workspace:*"