refactor: application dependency management (#2363)

This commit is contained in:
Mo
2023-07-23 15:54:31 -05:00
committed by GitHub
parent e698b1c990
commit a77535456c
299 changed files with 7415 additions and 4890 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,147 @@
export const TYPES = {
// System
DeviceInterface: Symbol.for('DeviceInterface'),
AlertService: Symbol.for('AlertService'),
Crypto: Symbol.for('Crypto'),
// Services
InternalEventBus: Symbol.for('InternalEventBus'),
PayloadManager: Symbol.for('PayloadManager'),
ItemManager: Symbol.for('ItemManager'),
MutatorService: Symbol.for('MutatorService'),
DiskStorageService: Symbol.for('DiskStorageService'),
UserEventService: Symbol.for('UserEventService'),
InMemoryStore: Symbol.for('InMemoryStore'),
KeySystemKeyManager: Symbol.for('KeySystemKeyManager'),
EncryptionService: Symbol.for('EncryptionService'),
ChallengeService: Symbol.for('ChallengeService'),
DeprecatedHttpService: Symbol.for('DeprecatedHttpService'),
HttpService: Symbol.for('HttpService'),
LegacyApiService: Symbol.for('LegacyApiService'),
UserServer: Symbol.for('UserServer'),
UserRequestServer: Symbol.for('UserRequestServer'),
UserApiService: Symbol.for('UserApiService'),
SubscriptionServer: Symbol.for('SubscriptionServer'),
SubscriptionApiService: Symbol.for('SubscriptionApiService'),
WebSocketServer: Symbol.for('WebSocketServer'),
WebSocketApiService: Symbol.for('WebSocketApiService'),
WebSocketsService: Symbol.for('WebSocketsService'),
SessionManager: Symbol.for('SessionManager'),
SubscriptionManager: Symbol.for('SubscriptionManager'),
HistoryManager: Symbol.for('HistoryManager'),
SyncService: Symbol.for('SyncService'),
ProtectionService: Symbol.for('ProtectionService'),
UserService: Symbol.for('UserService'),
KeyRecoveryService: Symbol.for('KeyRecoveryService'),
SingletonManager: Symbol.for('SingletonManager'),
PreferencesService: Symbol.for('PreferencesService'),
SettingsService: Symbol.for('SettingsService'),
FeaturesService: Symbol.for('FeaturesService'),
ComponentManager: Symbol.for('ComponentManager'),
MfaService: Symbol.for('MfaService'),
StatusService: Symbol.for('StatusService'),
MigrationService: Symbol.for('MigrationService'),
FileService: Symbol.for('FileService'),
IntegrityService: Symbol.for('IntegrityService'),
ListedService: Symbol.for('ListedService'),
ActionsService: Symbol.for('ActionsService'),
AuthenticatorApiService: Symbol.for('AuthenticatorApiService'),
AuthenticatorManager: Symbol.for('AuthenticatorManager'),
AuthApiService: Symbol.for('AuthApiService'),
AuthManager: Symbol.for('AuthManager'),
RevisionApiService: Symbol.for('RevisionApiService'),
RevisionManager: Symbol.for('RevisionManager'),
ContactService: Symbol.for('ContactService'),
VaultService: Symbol.for('VaultService'),
SharedVaultService: Symbol.for('SharedVaultService'),
AsymmetricMessageService: Symbol.for('AsymmetricMessageService'),
SelfContactManager: Symbol.for('SelfContactManager'),
EncryptionOperators: Symbol.for('EncryptionOperators'),
RootKeyManager: Symbol.for('RootKeyManager'),
ItemsEncryptionService: Symbol.for('ItemsEncryptionService'),
// Servers
RevisionServer: Symbol.for('RevisionServer'),
AuthenticatorServer: Symbol.for('AuthenticatorServer'),
AuthServer: Symbol.for('AuthServer'),
SharedVaultInvitesServer: Symbol.for('SharedVaultInvitesServer'),
SharedVaultServer: Symbol.for('SharedVaultServer'),
SharedVaultUsersServer: Symbol.for('SharedVaultUsersServer'),
AsymmetricMessageServer: Symbol.for('AsymmetricMessageServer'),
// Desktop Services
FilesBackupService: Symbol.for('FilesBackupService'),
HomeServerService: Symbol.for('HomeServerService'),
// Usecases
SignInWithRecoveryCodes: Symbol.for('SignInWithRecoveryCodes'),
GetRecoveryCodes: Symbol.for('GetRecoveryCodes'),
AddAuthenticator: Symbol.for('AddAuthenticator'),
ListAuthenticators: Symbol.for('ListAuthenticators'),
DeleteAuthenticator: Symbol.for('DeleteAuthenticator'),
GetAuthenticatorAuthenticationOptions: Symbol.for('GetAuthenticatorAuthenticationOptions'),
GetAuthenticatorAuthenticationResponse: Symbol.for('GetAuthenticatorAuthenticationResponse'),
ListRevisions: Symbol.for('ListRevisions'),
GetRevision: Symbol.for('GetRevision'),
DeleteRevision: Symbol.for('DeleteRevision'),
ImportDataUseCase: Symbol.for('ImportDataUseCase'),
RemoveItemsLocally: Symbol.for('RemoveItemsLocally'),
FindContact: Symbol.for('FindContact'),
GetAllContacts: Symbol.for('GetAllContacts'),
CreateOrEditContact: Symbol.for('CreateOrEditContact'),
EditContact: Symbol.for('EditContact'),
ValidateItemSigner: Symbol.for('ValidateItemSigner'),
GetVault: Symbol.for('GetVault'),
ChangeVaultKeyOptions: Symbol.for('ChangeVaultKeyOptions'),
MoveItemsToVault: Symbol.for('MoveItemsToVault'),
CreateVault: Symbol.for('CreateVault'),
RemoveItemFromVault: Symbol.for('RemoveItemFromVault'),
DeleteVault: Symbol.for('DeleteVault'),
RotateVaultKey: Symbol.for('RotateVaultKey'),
CreateSharedVault: Symbol.for('CreateSharedVault'),
HandleKeyPairChange: Symbol.for('HandleKeyPairChange'),
NotifyVaultUsersOfKeyRotation: Symbol.for('NotifyVaultUsersOfKeyRotation'),
SendVaultDataChangedMessage: Symbol.for('SendVaultDataChangedMessage'),
SendVaultKeyChangedMessage: Symbol.for('SendVaultKeyChangedMessage'),
GetTrustedPayload: Symbol.for('GetTrustedPayload'),
GetUntrustedPayload: Symbol.for('GetUntrustedPayload'),
GetVaultContacts: Symbol.for('GetVaultContacts'),
AcceptVaultInvite: Symbol.for('AcceptVaultInvite'),
InviteToVault: Symbol.for('InviteToVault'),
LeaveVault: Symbol.for('LeaveVault'),
DeleteThirdPartyVault: Symbol.for('DeleteThirdPartyVault'),
ShareContactWithVault: Symbol.for('ShareContactWithVault'),
ConvertToSharedVault: Symbol.for('ConvertToSharedVault'),
DeleteSharedVault: Symbol.for('DeleteSharedVault'),
RemoveVaultMember: Symbol.for('RemoveVaultMember'),
GetVaultUsers: Symbol.for('GetSharedVaultUsers'),
ResendAllMessages: Symbol.for('ResendAllMessages'),
ReuploadAllInvites: Symbol.for('ReuploadAllInvites'),
ReuploadInvite: Symbol.for('ReuploadInvite'),
GetInboundMessages: Symbol.for('GetInboundMessages'),
GetOutboundMessages: Symbol.for('GetOutboundMessages'),
HandleRootKeyChangedMessage: Symbol.for('HandleRootKeyChangedMessage'),
ProcessAcceptedVaultInvite: Symbol.for('ProcessAcceptedVaultInvite'),
ResendMessage: Symbol.for('ResendMessage'),
SendMessage: Symbol.for('SendMessage'),
SendOwnContactChangeMessage: Symbol.for('SendOwnContactChangeMessage'),
DecryptMessage: Symbol.for('DecryptMessage'),
DecryptOwnMessage: Symbol.for('DecryptOwnMessage'),
EncryptMessage: Symbol.for('EncryptMessage'),
GetMessageAdditionalData: Symbol.for('GetMessageAdditionalData'),
SendVaultInvite: Symbol.for('SendVaultInvite'),
ReplaceContactData: Symbol.for('ReplaceContactData'),
CreateNewDefaultItemsKey: Symbol.for('CreateNewDefaultItemsKey'),
CreateNewItemsKeyWithRollback: Symbol.for('CreateNewItemsKeyWithRollback'),
FindDefaultItemsKey: Symbol.for('FindDefaultItemsKey'),
DecryptErroredTypeAPayloads: Symbol.for('DecryptErroredTypeAPayloads'),
DecryptTypeAPayload: Symbol.for('DecryptTypeAPayload'),
DecryptTypeAPayloadWithKeyLookup: Symbol.for('DecryptTypeAPayloadWithKeyLookup'),
EncryptTypeAPayload: Symbol.for('EncryptTypeAPayload'),
EncryptTypeAPayloadWithKeyLookup: Symbol.for('EncryptTypeAPayloadWithKeyLookup'),
DecryptBackupFile: Symbol.for('DecryptBackupFile'),
// Mappers
SessionStorageMapper: Symbol.for('SessionStorageMapper'),
LegacySessionStorageMapper: Symbol.for('LegacySessionStorageMapper'),
}

View File

@@ -0,0 +1,14 @@
export function isDeinitable(service: unknown): service is { deinit(): void } {
if (!service) {
throw new Error('Service is undefined')
}
return typeof (service as { deinit(): void }).deinit === 'function'
}
export function canBlockDeinit(service: unknown): service is { blockDeinit(): Promise<void> } {
if (!service) {
throw new Error('Service is undefined')
}
return typeof (service as { blockDeinit(): Promise<void> }).blockDeinit === 'function'
}

View File

@@ -1,6 +1,5 @@
import { EncryptedPayloadInterface, HistoryEntry } from '@standardnotes/models'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { RevisionClientInterface } from '@standardnotes/services'
import { EncryptionProviderInterface, RevisionClientInterface } from '@standardnotes/services'
jest.mock('@standardnotes/models', () => {
const original = jest.requireActual('@standardnotes/models')
@@ -32,11 +31,13 @@ describe('GetRevision', () => {
enc_item_key: 'foobar',
auth_hash: 'foobar',
created_at: '2021-01-01T00:00:00.000Z',
updated_at: '2021-01-01T00:00:00.000Z'
updated_at: '2021-01-01T00:00:00.000Z',
} as jest.Mocked<Revision>)
encryptionService = {} as jest.Mocked<EncryptionProviderInterface>
encryptionService.getEmbeddedPayloadAuthenticatedData = jest.fn().mockReturnValue({ u: '00000000-0000-0000-0000-000000000000' })
encryptionService.getEmbeddedPayloadAuthenticatedData = jest
.fn()
.mockReturnValue({ u: '00000000-0000-0000-0000-000000000000' })
const encryptedPayload = {
content: 'foobar',
} as jest.Mocked<EncryptedPayloadInterface>

View File

@@ -1,5 +1,5 @@
import { ServerItemResponse } from '@standardnotes/responses'
import { RevisionClientInterface } from '@standardnotes/services'
import { EncryptionProviderInterface, RevisionClientInterface } from '@standardnotes/services'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import {
EncryptedPayload,
@@ -9,7 +9,6 @@ import {
NoteContent,
PayloadTimestampDefaults,
} from '@standardnotes/models'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { GetRevisionDTO } from './GetRevisionDTO'

View File

@@ -1,10 +1,10 @@
import {
AuthClientInterface,
EncryptionProviderInterface,
InternalEventBusInterface,
KeyValueStoreInterface,
SessionsClientInterface,
} from '@standardnotes/services'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { AnyKeyParamsContent } from '@standardnotes/common'
import { DecryptedPayloadInterface, RootKeyContent, RootKeyInterface } from '@standardnotes/models'
@@ -20,14 +20,8 @@ describe('SignInWithRecoveryCodes', () => {
let sessionManager: SessionsClientInterface
let internalEventBus: InternalEventBusInterface
const createUseCase = () => new SignInWithRecoveryCodes(
authManager,
encryptionService,
inMemoryStore,
crypto,
sessionManager,
internalEventBus,
)
const createUseCase = () =>
new SignInWithRecoveryCodes(authManager, encryptionService, inMemoryStore, crypto, sessionManager, internalEventBus)
beforeEach(() => {
authManager = {} as jest.Mocked<AuthClientInterface>
@@ -54,12 +48,7 @@ describe('SignInWithRecoveryCodes', () => {
encryptionService.hasAccount = jest.fn()
encryptionService.computeRootKey = jest.fn().mockReturnValue(rootKey)
encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(true)
encryptionService.supportedVersions = jest.fn().mockReturnValue([
'001',
'002',
'003',
'004',
])
encryptionService.supportedVersions = jest.fn().mockReturnValue(['001', '002', '003', '004'])
encryptionService.isVersionNewerThanLibraryVersion = jest.fn()
inMemoryStore = {} as jest.Mocked<KeyValueStoreInterface<string>>
@@ -82,7 +71,11 @@ describe('SignInWithRecoveryCodes', () => {
encryptionService.hasAccount = jest.fn().mockReturnValue(true)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' })
const result = await useCase.execute({
recoveryCodes: 'recovery-codes',
password: 'foobar',
username: 'test@test.te',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('Tried to sign in when an account already exists.')
@@ -92,7 +85,11 @@ describe('SignInWithRecoveryCodes', () => {
authManager.recoveryKeyParams = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' })
const result = await useCase.execute({
recoveryCodes: 'recovery-codes',
password: 'foobar',
username: 'test@test.te',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('Could not retrieve recovery key params')
@@ -102,10 +99,16 @@ describe('SignInWithRecoveryCodes', () => {
encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' })
const result = await useCase.execute({
recoveryCodes: 'recovery-codes',
password: 'foobar',
username: 'test@test.te',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('Your account was created on a platform with higher security capabilities than this browser supports. If we attempted to generate your login keys here, it would take hours. Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in.')
expect(result.getError()).toEqual(
'Your account was created on a platform with higher security capabilities than this browser supports. If we attempted to generate your login keys here, it would take hours. Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in.',
)
})
it('should fail if key params has unsupported version', async () => {
@@ -123,10 +126,16 @@ describe('SignInWithRecoveryCodes', () => {
encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' })
const result = await useCase.execute({
recoveryCodes: 'recovery-codes',
password: 'foobar',
username: 'test@test.te',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in.')
expect(result.getError()).toEqual(
'This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in.',
)
})
it('should fail if key params has expired version', async () => {
@@ -144,17 +153,27 @@ describe('SignInWithRecoveryCodes', () => {
encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' })
const result = await useCase.execute({
recoveryCodes: 'recovery-codes',
password: 'foobar',
username: 'test@test.te',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.com/help/security for more information.')
expect(result.getError()).toEqual(
'The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.com/help/security for more information.',
)
})
it('should fail if the sign in with recovery code fails', async () => {
authManager.signInWithRecoveryCodes = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' })
const result = await useCase.execute({
recoveryCodes: 'recovery-codes',
password: 'foobar',
username: 'test@test.te',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('Could not sign in with recovery code')
@@ -168,11 +187,15 @@ describe('SignInWithRecoveryCodes', () => {
uuid: '1-2-3',
email: 'test@test.te',
protocolVersion: '004',
}
},
})
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' })
const result = await useCase.execute({
recoveryCodes: 'recovery-codes',
password: 'foobar',
username: 'test@test.te',
})
expect(result.isFailed()).toBe(false)
})

View File

@@ -4,6 +4,7 @@ import {
AccountEvent,
AuthClientInterface,
EXPIRED_PROTOCOL_VERSION,
EncryptionProviderInterface,
InternalEventBusInterface,
InternalEventPublishStrategy,
KeyValueStoreInterface,
@@ -12,7 +13,7 @@ import {
UNSUPPORTED_KEY_DERIVATION,
UNSUPPORTED_PROTOCOL_VERSION,
} from '@standardnotes/services'
import { CreateAnyKeyParams, EncryptionProviderInterface, SNRootKey } from '@standardnotes/encryption'
import { CreateAnyKeyParams, SNRootKey } from '@standardnotes/encryption'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { SignInWithRecoveryCodesDTO } from './SignInWithRecoveryCodesDTO'

View File

@@ -7,10 +7,10 @@ import {
MutatorClientInterface,
PreferenceServiceInterface,
} from '@standardnotes/services'
import { SNSessionManager } from '../Services/Session/SessionManager'
import { SessionManager } from '../Services/Session/SessionManager'
import { ApplicationIdentifier } from '@standardnotes/common'
import { ItemManager } from '@Lib/Services/Items/ItemManager'
import { ChallengeService, SNSingletonManager, SNFeaturesService, DiskStorageService } from '@Lib/Services'
import { ChallengeService, SingletonManager, FeaturesService, DiskStorageService } from '@Lib/Services'
import { LegacySession, MapperInterface } from '@standardnotes/domain-core'
export type MigrationServices = {
@@ -18,12 +18,12 @@ export type MigrationServices = {
deviceInterface: DeviceInterface
storageService: DiskStorageService
challengeService: ChallengeService
sessionManager: SNSessionManager
sessionManager: SessionManager
backups?: BackupServiceInterface
itemManager: ItemManager
mutator: MutatorClientInterface
singletonManager: SNSingletonManager
featuresService: SNFeaturesService
singletonManager: SingletonManager
featuresService: FeaturesService
preferences: PreferenceServiceInterface
environment: Environment
platform: Platform

View File

@@ -1,7 +1,6 @@
import { removeFromArray } from '@standardnotes/utils'
import { SNRootKey } from '@standardnotes/encryption'
import { ChallengeService } from '../Challenge'
import { ListedService } from '../Listed/ListedService'
import { ActionResponse, DeprecatedHttpResponse } from '@standardnotes/responses'
import { ItemManager } from '@Lib/Services/Items/ItemManager'
import {
@@ -21,8 +20,6 @@ import {
TransferPayload,
ItemContent,
} from '@standardnotes/models'
import { SNSyncService } from '../Sync/SyncService'
import { PayloadManager } from '../Payloads/PayloadManager'
import { DeprecatedHttpService } from '../Api/DeprecatedHttpService'
import {
AbstractService,
@@ -53,20 +50,17 @@ type PayloadRequestHandler = (uuid: string) => TransferPayload | undefined
* `post`: sends an item's data to a remote service. This is used for example by Listed
* to allow publishing a note to a user's blog.
*/
export class SNActionsService extends AbstractService {
export class ActionsService extends AbstractService {
private previousPasswords: string[] = []
private payloadRequestHandlers: PayloadRequestHandler[] = []
constructor(
private itemManager: ItemManager,
private alertService: AlertService,
public deviceInterface: DeviceInterface,
private device: DeviceInterface,
private httpService: DeprecatedHttpService,
private payloadManager: PayloadManager,
private encryptionService: EncryptionService,
private syncService: SNSyncService,
private challengeService: ChallengeService,
private listedService: ListedService,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -76,13 +70,10 @@ export class SNActionsService extends AbstractService {
public override deinit(): void {
;(this.itemManager as unknown) = undefined
;(this.alertService as unknown) = undefined
;(this.deviceInterface as unknown) = undefined
;(this.device as unknown) = undefined
;(this.httpService as unknown) = undefined
;(this.payloadManager as unknown) = undefined
;(this.listedService as unknown) = undefined
;(this.challengeService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.syncService as unknown) = undefined
this.payloadRequestHandlers.length = 0
this.previousPasswords.length = 0
super.deinit()
@@ -323,7 +314,7 @@ export class SNActionsService extends AbstractService {
}
private handleShowAction(action: Action) {
void this.deviceInterface.openUrl(action.url)
void this.device.openUrl(action.url)
return {} as ActionResponse
}

View File

@@ -1,7 +1,7 @@
import { joinPaths } from '@standardnotes/utils'
import {
AbstractService,
ApiServiceInterface,
LegacyApiServiceInterface,
InternalEventBusInterface,
IntegrityApiInterface,
ItemsServerInterface,
@@ -86,10 +86,10 @@ const V0_API_VERSION = '20200115'
type InvalidSessionObserver = (revoked: boolean) => void
export class SNApiService
export class LegacyApiService
extends AbstractService<ApiServiceEvent, ApiServiceEventData>
implements
ApiServiceInterface,
LegacyApiServiceInterface,
FilesApiInterface,
IntegrityApiInterface,
ItemsServerInterface,

View File

@@ -2,7 +2,7 @@ import { InternalEventBusInterface } from '@standardnotes/services'
import { WebSocketApiServiceInterface } from '@standardnotes/api'
import { StorageKey, DiskStorageService } from '@Lib/index'
import { SNWebSocketsService } from './WebsocketsService'
import { WebSocketsService } from './WebsocketsService'
describe('webSocketsService', () => {
const webSocketUrl = ''
@@ -12,7 +12,7 @@ describe('webSocketsService', () => {
let internalEventBus: InternalEventBusInterface
const createService = () => {
return new SNWebSocketsService(storageService, webSocketUrl, webSocketApiService, internalEventBus)
return new WebSocketsService(storageService, webSocketUrl, webSocketApiService, internalEventBus)
}
beforeEach(() => {

View File

@@ -9,7 +9,7 @@ import {
import { WebSocketApiServiceInterface } from '@standardnotes/api'
import { WebSocketsServiceEvent } from './WebSocketsServiceEvent'
export class SNWebSocketsService extends AbstractService<WebSocketsServiceEvent, UserRolesChangedEvent> {
export class WebSocketsService extends AbstractService<WebSocketsServiceEvent, UserRolesChangedEvent> {
private webSocket?: WebSocket
constructor(

View File

@@ -158,7 +158,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic
return { wrappingKey }
}
public isPasscodeLocked() {
public isPasscodeLocked(): Promise<boolean> {
return this.encryptionService.isPasscodeLocked()
}
@@ -260,7 +260,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic
delete this.challengeOperations[challenge.id]
}
public cancelChallenge(challenge: Challenge) {
public cancelChallenge(challenge: Challenge): void {
const operation = this.challengeOperations[challenge.id]
operation.cancel()
@@ -274,7 +274,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic
this.deleteChallengeOperation(operation)
}
public async submitValuesForChallenge(challenge: Challenge, values: ChallengeValue[]) {
public async submitValuesForChallenge(challenge: Challenge, values: ChallengeValue[]): Promise<void> {
if (values.length === 0) {
throw Error('Attempting to submit 0 values for challenge')
}

View File

@@ -10,14 +10,14 @@ import {
PreferenceServiceInterface,
} from '@standardnotes/services'
import { ItemManager } from '@Lib/Services/Items/ItemManager'
import { SNFeaturesService } from '@Lib/Services/Features/FeaturesService'
import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
import { SNComponentManager } from './ComponentManager'
import { SNSyncService } from '../Sync/SyncService'
import { SyncService } from '../Sync/SyncService'
describe('featuresService', () => {
let items: ItemManagerInterface
let mutator: MutatorClientInterface
let features: SNFeaturesService
let features: FeaturesService
let alerts: AlertService
let sync: SyncServiceInterface
let prefs: PreferenceServiceInterface
@@ -47,7 +47,7 @@ describe('featuresService', () => {
attachEvent: jest.fn(),
} as unknown as Window & typeof globalThis
sync = {} as jest.Mocked<SNSyncService>
sync = {} as jest.Mocked<SyncService>
sync.sync = jest.fn()
items = {} as jest.Mocked<ItemManager>
@@ -61,7 +61,7 @@ describe('featuresService', () => {
mutator.changeItem = jest.fn()
mutator.changeFeatureRepo = jest.fn()
features = {} as jest.Mocked<SNFeaturesService>
features = {} as jest.Mocked<FeaturesService>
prefs = {} as jest.Mocked<SNPreferencesService>
prefs.addEventObserver = jest.fn()

View File

@@ -1,4 +1,4 @@
import { SNFeaturesService } from '@Lib/Services/Features/FeaturesService'
import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
import { ContentType } from '@standardnotes/domain-core'
import {
ActionObserver,
@@ -94,7 +94,7 @@ export class SNComponentManager
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private features: SNFeaturesService,
private features: FeaturesService,
private preferences: PreferenceServiceInterface,
protected alerts: AlertService,
private environment: Environment,

View File

@@ -11,7 +11,7 @@ import {
ItemManagerInterface,
SyncServiceInterface,
} from '@standardnotes/services'
import { SNFeaturesService } from '@Lib/Services'
import { FeaturesService } from '@Lib/Services'
import {
ActionObserver,
ComponentEventObserver,
@@ -100,7 +100,7 @@ export class ComponentViewer implements ComponentViewerInterface {
sync: SyncServiceInterface
alerts: AlertService
preferences: PreferenceServiceInterface
features: SNFeaturesService
features: FeaturesService
},
private options: {
item: ComponentViewerItem

View File

@@ -1,15 +1,15 @@
import { ItemInterface, SNFeatureRepo } from '@standardnotes/models'
import { SNSyncService } from '../Sync/SyncService'
import { SyncService } from '../Sync/SyncService'
import { SettingName } from '@standardnotes/settings'
import { SNFeaturesService } from '@Lib/Services/Features'
import { FeaturesService } from '@Lib/Services/Features'
import { RoleName, ContentType } from '@standardnotes/domain-core'
import { FeatureIdentifier, GetFeatures } from '@standardnotes/features'
import { SNWebSocketsService } from '../Api/WebsocketsService'
import { SNSettingsService } from '../Settings'
import { WebSocketsService } from '../Api/WebsocketsService'
import { SettingsService } from '../Settings'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import {
AlertService,
ApiServiceInterface,
LegacyApiServiceInterface,
FeaturesEvent,
FeatureStatus,
InternalEventBusInterface,
@@ -23,7 +23,7 @@ import {
UserClientInterface,
UserService,
} from '@standardnotes/services'
import { SNApiService, SNSessionManager } from '../Api'
import { LegacyApiService, SessionManager } from '../Api'
import { ItemManager } from '../Items'
import { DiskStorageService } from '../Storage/DiskStorageService'
import { SettingsClientInterface } from '../Settings/SettingsClientInterface'
@@ -33,8 +33,8 @@ describe('FeaturesService', () => {
let itemManager: ItemManagerInterface
let mutator: MutatorClientInterface
let subscriptions: SubscriptionManagerInterface
let apiService: ApiServiceInterface
let webSocketsService: SNWebSocketsService
let apiService: LegacyApiServiceInterface
let webSocketsService: WebSocketsService
let settingsService: SettingsClientInterface
let userService: UserClientInterface
let syncService: SyncServiceInterface
@@ -46,7 +46,7 @@ describe('FeaturesService', () => {
let internalEventBus: InternalEventBusInterface
const createService = () => {
return new SNFeaturesService(
return new FeaturesService(
storageService,
itemManager,
mutator,
@@ -72,7 +72,7 @@ describe('FeaturesService', () => {
storageService.setValue = jest.fn()
storageService.getValue = jest.fn()
apiService = {} as jest.Mocked<SNApiService>
apiService = {} as jest.Mocked<LegacyApiService>
apiService.addEventObserver = jest.fn()
apiService.isThirdPartyHostUsed = jest.fn().mockReturnValue(false)
@@ -92,23 +92,23 @@ describe('FeaturesService', () => {
subscriptions.getOnlineSubscription = jest.fn()
subscriptions.addEventObserver = jest.fn()
webSocketsService = {} as jest.Mocked<SNWebSocketsService>
webSocketsService = {} as jest.Mocked<WebSocketsService>
webSocketsService.addEventObserver = jest.fn()
settingsService = {} as jest.Mocked<SNSettingsService>
settingsService = {} as jest.Mocked<SettingsService>
settingsService.updateSetting = jest.fn()
userService = {} as jest.Mocked<UserService>
userService.addEventObserver = jest.fn()
syncService = {} as jest.Mocked<SNSyncService>
syncService = {} as jest.Mocked<SyncService>
syncService.sync = jest.fn()
alertService = {} as jest.Mocked<AlertService>
alertService.confirm = jest.fn().mockReturnValue(true)
alertService.alert = jest.fn()
sessionManager = {} as jest.Mocked<SNSessionManager>
sessionManager = {} as jest.Mocked<SessionManager>
sessionManager.isSignedIntoFirstPartyServer = jest.fn()
sessionManager.getUser = jest.fn()

View File

@@ -4,7 +4,7 @@ import { ClientDisplayableError } from '@standardnotes/responses'
import { RoleName, ContentType } from '@standardnotes/domain-core'
import { PROD_OFFLINE_FEATURES_URL } from '../../Hosts'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { SNWebSocketsService } from '../Api/WebsocketsService'
import { WebSocketsService } from '../Api/WebsocketsService'
import { WebSocketsServiceEvent } from '../Api/WebSocketsServiceEvent'
import { TRUSTED_CUSTOM_EXTENSIONS_HOSTS, TRUSTED_FEATURE_HOSTS } from '@Lib/Hosts'
import { UserRolesChangedEvent } from '@standardnotes/domain-events'
@@ -39,7 +39,7 @@ import {
StorageKey,
MutatorClientInterface,
StorageServiceInterface,
ApiServiceInterface,
LegacyApiServiceInterface,
ItemManagerInterface,
SyncServiceInterface,
SessionsClientInterface,
@@ -47,6 +47,8 @@ import {
SubscriptionManagerInterface,
AccountEvent,
SubscriptionManagerEvent,
ApplicationEvent,
ApplicationStageChangedEventPayload,
} from '@standardnotes/services'
import { DownloadRemoteThirdPartyFeatureUseCase } from './UseCase/DownloadRemoteThirdPartyFeature'
@@ -56,7 +58,7 @@ import { SettingsClientInterface } from '../Settings/SettingsClientInterface'
type GetOfflineSubscriptionDetailsResponse = OfflineSubscriptionEntitlements | ClientDisplayableError
export class SNFeaturesService
export class FeaturesService
extends AbstractService<FeaturesEvent>
implements FeaturesClientInterface, InternalEventHandlerInterface
{
@@ -71,8 +73,8 @@ export class SNFeaturesService
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private subscriptions: SubscriptionManagerInterface,
private api: ApiServiceInterface,
sockets: SNWebSocketsService,
private api: LegacyApiServiceInterface,
sockets: WebSocketsService,
private settings: SettingsClientInterface,
private user: UserClientInterface,
private sync: SyncServiceInterface,
@@ -152,20 +154,19 @@ export class SNFeaturesService
const { userRoles } = event.payload as MetaReceivedData
void this.updateOnlineRolesWithNewValues(userRoles.map((role) => role.name))
}
}
override async handleApplicationStage(stage: ApplicationStage): Promise<void> {
if (stage === ApplicationStage.FullSyncCompleted_13) {
if (!this.hasFirstPartyOnlineSubscription()) {
const offlineRepo = this.getOfflineRepo()
if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
if (stage === ApplicationStage.FullSyncCompleted_13) {
if (!this.hasFirstPartyOnlineSubscription()) {
const offlineRepo = this.getOfflineRepo()
if (offlineRepo) {
void this.downloadOfflineRoles(offlineRepo)
if (offlineRepo) {
void this.downloadOfflineRoles(offlineRepo)
}
}
}
}
return super.handleApplicationStage(stage)
}
public enableExperimentalFeature(identifier: FeatureIdentifier): void {

View File

@@ -9,13 +9,17 @@ import {
import {
AlertService,
API_MESSAGE_FAILED_DOWNLOADING_EXTENSION,
ApiServiceInterface,
LegacyApiServiceInterface,
ItemManagerInterface,
} from '@standardnotes/services'
import { isString } from '@standardnotes/utils'
export class DownloadRemoteThirdPartyFeatureUseCase {
constructor(private api: ApiServiceInterface, private items: ItemManagerInterface, private alerts: AlertService) {}
constructor(
private api: LegacyApiServiceInterface,
private items: ItemManagerInterface,
private alerts: AlertService,
) {}
async execute(url: string): Promise<ComponentInterface | undefined> {
const response = await this.api.downloadFeatureUrl(url)

View File

@@ -30,7 +30,7 @@ const LargeEntryDeltaThreshold = 25
* 2. Remote server history. Entries are automatically added by the server and must be
* retrieved per item via an API call.
*/
export class SNHistoryManager extends AbstractService implements HistoryServiceInterface {
export class HistoryManager extends AbstractService implements HistoryServiceInterface {
private removeChangeObserver: () => void
/**

View File

@@ -1,9 +1,13 @@
import { ItemsKeyInterface } from '@standardnotes/models'
import { dateSorted } from '@standardnotes/utils'
import { SNRootKeyParams, EncryptionProviderInterface } from '@standardnotes/encryption'
import { SNRootKeyParams } from '@standardnotes/encryption'
import { DecryptionQueueItem, KeyRecoveryOperationResult } from './Types'
import { serverKeyParamsAreSafe } from './Utils'
import { ChallengeServiceInterface, DecryptItemsKeyByPromptingUser } from '@standardnotes/services'
import {
ChallengeServiceInterface,
DecryptItemsKeyByPromptingUser,
EncryptionProviderInterface,
} from '@standardnotes/services'
import { ItemManager } from '../Items'
import { ContentType } from '@standardnotes/domain-core'

View File

@@ -11,11 +11,11 @@ import {
getIncrementedDirtyIndex,
ContentTypeUsesRootKeyEncryption,
} from '@standardnotes/models'
import { SNSyncService } from '../Sync/SyncService'
import { SyncService } from '../Sync/SyncService'
import { DiskStorageService } from '../Storage/DiskStorageService'
import { PayloadManager } from '../Payloads/PayloadManager'
import { ChallengeService } from '../Challenge'
import { SNApiService } from '@Lib/Services/Api/ApiService'
import { LegacyApiService } from '@Lib/Services/Api/ApiService'
import { ItemManager } from '../Items/ItemManager'
import { removeFromArray, Uuids } from '@standardnotes/utils'
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
@@ -33,6 +33,10 @@ import {
EncryptionService,
Challenge,
UserService,
InternalEventHandlerInterface,
InternalEventInterface,
ApplicationEvent,
ApplicationStageChangedEventPayload,
} from '@standardnotes/services'
import {
UndecryptableItemsStorage,
@@ -79,7 +83,10 @@ import { ContentType } from '@standardnotes/domain-core'
* but our current copy is not, we will ignore the incoming value until we can properly
* decrypt it.
*/
export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, DecryptedPayloadInterface[]> {
export class KeyRecoveryService
extends AbstractService<KeyRecoveryEvent, DecryptedPayloadInterface[]>
implements InternalEventHandlerInterface
{
private removeItemObserver: () => void
private decryptionQueue: DecryptionQueueItem[] = []
private isProcessingQueue = false
@@ -87,12 +94,12 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
constructor(
private itemManager: ItemManager,
private payloadManager: PayloadManager,
private apiService: SNApiService,
private apiService: LegacyApiService,
private encryptionService: EncryptionService,
private challengeService: ChallengeService,
private alertService: AlertService,
private storageService: DiskStorageService,
private syncService: SNSyncService,
private sync: SyncService,
private userService: UserService,
protected override internalEventBus: InternalEventBusInterface,
) {
@@ -126,7 +133,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
;(this.challengeService as unknown) = undefined
;(this.alertService as unknown) = undefined
;(this.storageService as unknown) = undefined
;(this.syncService as unknown) = undefined
;(this.sync as unknown) = undefined
;(this.userService as unknown) = undefined
this.removeItemObserver()
@@ -135,11 +142,12 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
super.deinit()
}
// eslint-disable-next-line @typescript-eslint/require-await
override async handleApplicationStage(stage: ApplicationStage): Promise<void> {
void super.handleApplicationStage(stage)
if (stage === ApplicationStage.LoadedDatabase_12) {
void this.processPersistedUndecryptables()
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
if (stage === ApplicationStage.LoadedDatabase_12) {
void this.processPersistedUndecryptables()
}
}
}
@@ -383,8 +391,8 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
await this.potentiallyPerformFallbackSignInToUpdateOutdatedLocalRootKey(serverParams)
}
if (this.syncService.isOutOfSync()) {
void this.syncService.sync({ checkIntegrity: true })
if (this.sync.isOutOfSync()) {
void this.sync.sync({ checkIntegrity: true })
}
}
@@ -500,7 +508,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
}
if (decryptedMatching.some((p) => p.dirty)) {
await this.syncService.sync()
await this.sync.sync()
}
await this.notifyEvent(KeyRecoveryEvent.KeysRecovered, decryptedMatching)

View File

@@ -1,27 +1,31 @@
import { SyncClientInterface } from './../Sync/SyncClientInterface'
import { isString, lastElement, sleep } from '@standardnotes/utils'
import { UuidString } from '@Lib/Types/UuidString'
import { ItemManager } from '@Lib/Services/Items/ItemManager'
import { DeprecatedHttpService } from '../Api/DeprecatedHttpService'
import { SettingName } from '@standardnotes/settings'
import { SNSettingsService } from '../Settings/SNSettingsService'
import { SettingsService } from '../Settings/SNSettingsService'
import { ListedClientInterface } from './ListedClientInterface'
import { SNApiService } from '../Api/ApiService'
import { LegacyApiService } from '../Api/ApiService'
import { isErrorResponse, ListedAccount, ListedAccountInfo, ListedAccountInfoResponse } from '@standardnotes/responses'
import { NoteMutator, SNActionsExtension, SNNote } from '@standardnotes/models'
import { AbstractService, InternalEventBusInterface, MutatorClientInterface } from '@standardnotes/services'
import {
AbstractService,
InternalEventBusInterface,
MutatorClientInterface,
SyncServiceInterface,
} from '@standardnotes/services'
import { SNProtectionService } from '../Protection'
import { ContentType } from '@standardnotes/domain-core'
export class ListedService extends AbstractService implements ListedClientInterface {
constructor(
private apiService: SNApiService,
private apiService: LegacyApiService,
private itemManager: ItemManager,
private settingsService: SNSettingsService,
private settingsService: SettingsService,
private httpSerivce: DeprecatedHttpService,
private protectionService: SNProtectionService,
private mutator: MutatorClientInterface,
private sync: SyncClientInterface,
private sync: SyncServiceInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)

View File

@@ -1,15 +1,15 @@
import { SettingName } from '@standardnotes/settings'
import { SNSettingsService } from '../Settings'
import { SettingsService } from '../Settings'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { SNFeaturesService } from '../Features/FeaturesService'
import { FeaturesService } from '../Features/FeaturesService'
import { AbstractService, InternalEventBusInterface, SignInStrings } from '@standardnotes/services'
export class SNMfaService extends AbstractService {
constructor(
private settingsService: SNSettingsService,
private settingsService: SettingsService,
private crypto: PureCryptoInterface,
private featuresService: SNFeaturesService,
private featuresService: FeaturesService,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)

View File

@@ -10,6 +10,9 @@ import {
ApplicationStage,
AbstractService,
DiagnosticInfo,
InternalEventHandlerInterface,
InternalEventInterface,
ApplicationStageChangedEventPayload,
} from '@standardnotes/services'
import { SnjsVersion, isRightVersionGreaterThanLeft } from '../../Version'
import { SNLog } from '@Lib/Log'
@@ -23,7 +26,7 @@ import { MigrationClasses } from '@Lib/Migrations/Versions'
* first launches, and also other steps after the application is unlocked, or after the
* first sync completes. Migrations live under /migrations and inherit from the base Migration class.
*/
export class SNMigrationService extends AbstractService {
export class MigrationService extends AbstractService implements InternalEventHandlerInterface {
private activeMigrations?: Migration[]
private baseMigration!: BaseMigration
@@ -44,7 +47,7 @@ export class SNMigrationService extends AbstractService {
public async initialize(): Promise<void> {
await this.runBaseMigrationPreRun()
const requiredMigrations = SNMigrationService.getRequiredMigrations(await this.getStoredSnjsVersion())
const requiredMigrations = MigrationService.getRequiredMigrations(await this.getStoredSnjsVersion())
this.activeMigrations = this.instantiateMigrationClasses(requiredMigrations)
@@ -70,13 +73,11 @@ export class SNMigrationService extends AbstractService {
await this.baseMigration.preRun()
}
/**
* Application instances will call this function directly when they arrive
* at a certain migratory state.
*/
public override async handleApplicationStage(stage: ApplicationStage): Promise<void> {
await super.handleApplicationStage(stage)
await this.handleStage(stage)
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
await this.handleStage(stage)
}
}
/**
@@ -89,7 +90,7 @@ export class SNMigrationService extends AbstractService {
}
public async hasPendingMigrations(): Promise<boolean> {
const requiredMigrations = SNMigrationService.getRequiredMigrations(await this.getStoredSnjsVersion())
const requiredMigrations = MigrationService.getRequiredMigrations(await this.getStoredSnjsVersion())
return requiredMigrations.length > 0 || (await this.baseMigration.needsKeychainRepair())
}

View File

@@ -1,7 +1,7 @@
import { SNUserPrefs, PrefKey, PrefValue, UserPrefsMutator, ItemContent, FillItemContent } from '@standardnotes/models'
import { ItemManager } from '../Items/ItemManager'
import { SNSingletonManager } from '../Singleton/SingletonManager'
import { SNSyncService } from '../Sync/SyncService'
import { SingletonManager } from '../Singleton/SingletonManager'
import { SyncService } from '../Sync/SyncService'
import {
AbstractService,
InternalEventBusInterface,
@@ -10,12 +10,16 @@ import {
PreferenceServiceInterface,
PreferencesServiceEvent,
MutatorClientInterface,
InternalEventHandlerInterface,
InternalEventInterface,
ApplicationEvent,
ApplicationStageChangedEventPayload,
} from '@standardnotes/services'
import { ContentType } from '@standardnotes/domain-core'
export class SNPreferencesService
extends AbstractService<PreferencesServiceEvent>
implements PreferenceServiceInterface
implements PreferenceServiceInterface, InternalEventHandlerInterface
{
private shouldReload = true
private reloading = false
@@ -24,10 +28,10 @@ export class SNPreferencesService
private removeSyncObserver?: () => void
constructor(
private singletons: SNSingletonManager,
private singletons: SingletonManager,
items: ItemManager,
private mutator: MutatorClientInterface,
private sync: SNSyncService,
private sync: SyncService,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -52,18 +56,19 @@ export class SNPreferencesService
super.deinit()
}
public override async handleApplicationStage(stage: ApplicationStage): Promise<void> {
await super.handleApplicationStage(stage)
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
if (stage === ApplicationStage.LoadedDatabase_12) {
/** Try to read preferences singleton from storage */
this.preferences = this.singletons.findSingleton<SNUserPrefs>(
ContentType.TYPES.UserPrefs,
SNUserPrefs.singletonPredicate,
)
if (stage === ApplicationStage.LoadedDatabase_12) {
/** Try to read preferences singleton from storage */
this.preferences = this.singletons.findSingleton<SNUserPrefs>(
ContentType.TYPES.UserPrefs,
SNUserPrefs.singletonPredicate,
)
if (this.preferences) {
void this.notifyEvent(PreferencesServiceEvent.PreferencesChanged)
if (this.preferences) {
void this.notifyEvent(PreferencesServiceEvent.PreferencesChanged)
}
}
}
}

View File

@@ -25,6 +25,10 @@ import {
TimingDisplayOption,
ProtectionsClientInterface,
MutatorClientInterface,
InternalEventHandlerInterface,
InternalEventInterface,
ApplicationEvent,
ApplicationStageChangedEventPayload,
} from '@standardnotes/services'
import { ContentType } from '@standardnotes/domain-core'
@@ -70,7 +74,10 @@ export const ProtectionSessionDurations = [
* like viewing a protected note, as well as managing how long that
* authentication should be valid for.
*/
export class SNProtectionService extends AbstractService<ProtectionEvent> implements ProtectionsClientInterface {
export class SNProtectionService
extends AbstractService<ProtectionEvent>
implements ProtectionsClientInterface, InternalEventHandlerInterface
{
private sessionExpiryTimeout = -1
private mobilePasscodeTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit
private mobileBiometricsTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit
@@ -93,13 +100,15 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
super.deinit()
}
override handleApplicationStage(stage: ApplicationStage): Promise<void> {
if (stage === ApplicationStage.LoadedDatabase_12) {
this.updateSessionExpiryTimer(this.getSessionExpiryDate())
this.mobilePasscodeTiming = this.getMobilePasscodeTiming()
this.mobileBiometricsTiming = this.getMobileBiometricsTiming()
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
if (stage === ApplicationStage.LoadedDatabase_12) {
this.updateSessionExpiryTimer(this.getSessionExpiryDate())
this.mobilePasscodeTiming = this.getMobilePasscodeTiming()
this.mobileBiometricsTiming = this.getMobileBiometricsTiming()
}
}
return Promise.resolve()
}
public hasProtectionSources(): boolean {

View File

@@ -30,7 +30,7 @@ import {
InternalFeatureService,
InternalFeature,
} from '@standardnotes/services'
import { Base64String, PkcKeyPair } from '@standardnotes/sncrypto-common'
import { Base64String, PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import {
SessionBody,
ErrorTag,
@@ -52,9 +52,9 @@ import * as Common from '@standardnotes/common'
import { RawStorageValue } from './Sessions/Types'
import { ShareToken } from './ShareToken'
import { SNApiService } from '../Api/ApiService'
import { LegacyApiService } from '../Api/ApiService'
import { DiskStorageService } from '../Storage/DiskStorageService'
import { SNWebSocketsService } from '../Api/WebsocketsService'
import { WebSocketsService } from '../Api/WebsocketsService'
import { Strings } from '@Lib/Strings'
import { UuidString } from '@Lib/Types/UuidString'
import { ChallengeResponse, ChallengeService } from '../Challenge'
@@ -78,7 +78,7 @@ const cleanedEmailString = (email: string) => {
* server credentials, such as the session token. It also exposes methods for registering
* for a new account, signing into an existing one, or changing an account password.
*/
export class SNSessionManager
export class SessionManager
extends AbstractService<SessionEvent>
implements SessionsClientInterface, InternalEventHandlerInterface
{
@@ -87,13 +87,14 @@ export class SNSessionManager
private session?: Session | LegacySession
constructor(
private diskStorageService: DiskStorageService,
private apiService: SNApiService,
private storage: DiskStorageService,
private apiService: LegacyApiService,
private userApiService: UserApiServiceInterface,
private alertService: AlertService,
private encryptionService: EncryptionService,
private crypto: PureCryptoInterface,
private challengeService: ChallengeService,
private webSocketsService: SNWebSocketsService,
private webSocketsService: WebSocketsService,
private httpService: HttpServiceInterface,
private sessionStorageMapper: MapperInterface<Session, Record<string, unknown>>,
private legacySessionStorageMapper: MapperInterface<LegacySession, Record<string, unknown>>,
@@ -118,7 +119,7 @@ export class SNSessionManager
override deinit(): void {
;(this.encryptionService as unknown) = undefined
;(this.diskStorageService as unknown) = undefined
;(this.storage as unknown) = undefined
;(this.apiService as unknown) = undefined
;(this.alertService as unknown) = undefined
;(this.challengeService as unknown) = undefined
@@ -141,17 +142,17 @@ export class SNSessionManager
this.apiService.setUser(user)
}
async initializeFromDisk() {
this.memoizeUser(this.diskStorageService.getValue(StorageKey.User))
async initializeFromDisk(): Promise<void> {
this.memoizeUser(this.storage.getValue(StorageKey.User))
if (!this.user) {
const legacyUuidLookup = this.diskStorageService.getValue<string>(StorageKey.LegacyUuid)
const legacyUuidLookup = this.storage.getValue<string>(StorageKey.LegacyUuid)
if (legacyUuidLookup) {
this.memoizeUser({ uuid: legacyUuidLookup, email: legacyUuidLookup })
}
}
const rawSession = this.diskStorageService.getValue<RawStorageValue>(StorageKey.Session)
const rawSession = this.storage.getValue<RawStorageValue>(StorageKey.Session)
if (rawSession) {
try {
const session =
@@ -286,7 +287,7 @@ export class SNSessionManager
email,
password,
false,
this.diskStorageService.isEphemeralSession(),
this.storage.isEphemeralSession(),
currentKeyParams?.version,
)
if (isErrorResponse(response)) {
@@ -630,10 +631,17 @@ export class SNSessionManager
if (!isErrorResponse(rawResponse)) {
if (InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
const eventData: UserKeyPairChangedEventData = {
oldKeyPair,
oldSigningKeyPair,
newKeyPair: parameters.newRootKey.encryptionKeyPair,
newSigningKeyPair: parameters.newRootKey.signingKeyPair,
previous:
oldKeyPair && oldSigningKeyPair
? {
encryption: oldKeyPair,
signing: oldSigningKeyPair,
}
: undefined,
current: {
encryption: parameters.newRootKey.encryptionKeyPair,
signing: parameters.newRootKey.signingKeyPair,
},
}
void this.notifyEvent(SessionEvent.UserKeyPairChanged, eventData)
@@ -692,7 +700,7 @@ export class SNSessionManager
}
private decodeDemoShareToken(token: Base64String): ShareToken {
const jsonString = this.encryptionService.crypto.base64Decode(token)
const jsonString = this.crypto.base64Decode(token)
return JSON.parse(jsonString)
}
@@ -712,7 +720,7 @@ export class SNSessionManager
await this.encryptionService.setRootKey(rootKey, wrappingKey)
this.memoizeUser(user)
this.diskStorageService.setValue(StorageKey.User, user)
this.storage.setValue(StorageKey.User, user)
void this.apiService.setHost(host)
this.httpService.setHost(host)

View File

@@ -1,11 +1,11 @@
import { SNApiService } from '../Api/ApiService'
import { LegacyApiService } from '../Api/ApiService'
import { SettingsGateway } from './SettingsGateway'
import { SNSessionManager } from '../Session/SessionManager'
import { SessionManager } from '../Session/SessionManager'
import { EmailBackupFrequency, SettingName } from '@standardnotes/settings'
import { AbstractService, InternalEventBusInterface } from '@standardnotes/services'
import { SettingsClientInterface } from './SettingsClientInterface'
export class SNSettingsService extends AbstractService implements SettingsClientInterface {
export class SettingsService extends AbstractService implements SettingsClientInterface {
private provider!: SettingsGateway
private frequencyOptionsLabels = {
[EmailBackupFrequency.Disabled]: 'No email backups',
@@ -14,8 +14,8 @@ export class SNSettingsService extends AbstractService implements SettingsClient
}
constructor(
private readonly sessionManager: SNSessionManager,
private readonly apiService: SNApiService,
private readonly sessionManager: SessionManager,
private readonly apiService: LegacyApiService,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)

View File

@@ -12,7 +12,7 @@ import {
Predicate,
} from '@standardnotes/models'
import { arrayByRemovingFromIndex, extendArray, UuidGenerator } from '@standardnotes/utils'
import { SNSyncService } from '../Sync/SyncService'
import { SyncService } from '../Sync/SyncService'
import {
AbstractService,
InternalEventBusInterface,
@@ -33,7 +33,7 @@ import { ContentType } from '@standardnotes/domain-core'
* 2. Items can override isSingleton, singletonPredicate, and strategyWhenConflictingWithItem (optional)
* to automatically gain singleton resolution.
*/
export class SNSingletonManager extends AbstractService implements SingletonManagerInterface {
export class SingletonManager extends AbstractService implements SingletonManagerInterface {
private resolveQueue: DecryptedItemInterface[] = []
private removeItemObserver!: () => void
@@ -43,7 +43,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
private itemManager: ItemManager,
private mutator: MutatorClientInterface,
private payloadManager: PayloadManager,
private syncService: SNSyncService,
private sync: SyncService,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -51,7 +51,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
}
public override deinit(): void {
;(this.syncService as unknown) = undefined
;(this.sync as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this.itemManager as unknown) = undefined
;(this.payloadManager as unknown) = undefined
@@ -93,7 +93,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
}
})
this.removeSyncObserver = this.syncService.addEventObserver(async (eventName) => {
this.removeSyncObserver = this.sync.addEventObserver(async (eventName) => {
if (
eventName === SyncEvent.DownloadFirstSyncCompleted ||
eventName === SyncEvent.SyncCompletedWithAllItemsUploaded
@@ -142,7 +142,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
* of a download-first request.
*/
if (handled.length > 0 && eventSource === SyncEvent.SyncCompletedWithAllItemsUploaded) {
await this.syncService?.sync({ sourceDescription: 'Resolve singletons for items' })
await this.sync?.sync({ sourceDescription: 'Resolve singletons for items' })
}
}
@@ -182,7 +182,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
}
/** Item not found, safe to create after full sync has completed */
if (!this.syncService.getLastSyncDate()) {
if (!this.sync.getLastSyncDate()) {
/**
* Add a temporary observer in case of long-running sync request, where
* the item we're looking for ends up resolving early or in the middle.
@@ -199,7 +199,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
}
})
await this.syncService.sync({ sourceDescription: 'Find or create singleton, before any sync has completed' })
await this.sync.sync({ sourceDescription: 'Find or create singleton, before any sync has completed' })
removeObserver()
@@ -233,7 +233,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
const item = await this.mutator.emitItemFromPayload(dirtyPayload, PayloadEmitSource.LocalInserted)
void this.syncService.sync({ sourceDescription: 'After find or create singleton' })
void this.sync.sync({ sourceDescription: 'After find or create singleton' })
return item as T
}
@@ -248,7 +248,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
}
/** Item not found, safe to create after full sync has completed */
if (!this.syncService.getLastSyncDate()) {
if (!this.sync.getLastSyncDate()) {
/**
* Add a temporary observer in case of long-running sync request, where
* the item we're looking for ends up resolving early or in the middle.
@@ -265,7 +265,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
}
})
await this.syncService.sync({ sourceDescription: 'Find or create singleton, before any sync has completed' })
await this.sync.sync({ sourceDescription: 'Find or create singleton, before any sync has completed' })
removeObserver()
@@ -292,7 +292,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana
const item = await this.mutator.emitItemFromPayload(dirtyPayload, PayloadEmitSource.LocalInserted)
void this.syncService.sync({ sourceDescription: 'After find or create singleton' })
void this.sync.sync({ sourceDescription: 'After find or create singleton' })
return item as T
}

View File

@@ -1,8 +1,33 @@
import { Copy, extendArray, UuidGenerator, Uuids } from '@standardnotes/utils'
import { SNLog } from '../../Log'
import { isErrorDecryptingParameters, SNRootKey } from '@standardnotes/encryption'
import * as Encryption from '@standardnotes/encryption'
import * as Services from '@standardnotes/services'
import {
KeyedDecryptionSplit,
KeyedEncryptionSplit,
SplitPayloadsByEncryptionType,
CreateEncryptionSplitWithKeyLookup,
isErrorDecryptingParameters,
SNRootKey,
} from '@standardnotes/encryption'
import {
AbstractService,
StorageServiceInterface,
InternalEventHandlerInterface,
StoragePersistencePolicies,
StorageValuesObject,
DeviceInterface,
InternalEventBusInterface,
InternalEventInterface,
ApplicationEvent,
ApplicationStageChangedEventPayload,
ApplicationStage,
ValueModesKeys,
StorageValueModes,
namespacedKey,
RawStorageKey,
WrappedStorageValue,
ValuesObjectRecord,
EncryptionProviderInterface,
} from '@standardnotes/services'
import {
CreateDecryptedLocalStorageContextPayload,
CreateDeletedLocalStorageContextPayload,
@@ -31,25 +56,28 @@ import { ContentType } from '@standardnotes/domain-core'
* decrypt the persisted key/values, and also a method to determine whether a particular
* key can decrypt wrapped storage.
*/
export class DiskStorageService extends Services.AbstractService implements Services.StorageServiceInterface {
private encryptionProvider!: Encryption.EncryptionProviderInterface
export class DiskStorageService
extends AbstractService
implements StorageServiceInterface, InternalEventHandlerInterface
{
private encryptionProvider!: EncryptionProviderInterface
private storagePersistable = false
private persistencePolicy!: Services.StoragePersistencePolicies
private persistencePolicy!: StoragePersistencePolicies
private needsPersist = false
private currentPersistPromise?: Promise<Services.StorageValuesObject>
private currentPersistPromise?: Promise<StorageValuesObject>
private values!: Services.StorageValuesObject
private values!: StorageValuesObject
constructor(
private deviceInterface: Services.DeviceInterface,
private deviceInterface: DeviceInterface,
private identifier: string,
protected override internalEventBus: Services.InternalEventBusInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
void this.setPersistencePolicy(Services.StoragePersistencePolicies.Default)
void this.setPersistencePolicy(StoragePersistencePolicies.Default)
}
public provideEncryptionProvider(provider: Encryption.EncryptionProviderInterface): void {
public provideEncryptionProvider(provider: EncryptionProviderInterface): void {
this.encryptionProvider = provider
}
@@ -60,21 +88,22 @@ export class DiskStorageService extends Services.AbstractService implements Serv
super.deinit()
}
override async handleApplicationStage(stage: Services.ApplicationStage) {
await super.handleApplicationStage(stage)
if (stage === Services.ApplicationStage.Launched_10) {
this.storagePersistable = true
if (this.needsPersist) {
void this.persistValuesToDisk()
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
if (stage === ApplicationStage.Launched_10) {
this.storagePersistable = true
if (this.needsPersist) {
void this.persistValuesToDisk()
}
}
}
}
public async setPersistencePolicy(persistencePolicy: Services.StoragePersistencePolicies) {
public async setPersistencePolicy(persistencePolicy: StoragePersistencePolicies) {
this.persistencePolicy = persistencePolicy
if (this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral) {
if (this.persistencePolicy === StoragePersistencePolicies.Ephemeral) {
await this.deviceInterface.clearNamespacedKeychainValue(this.identifier)
await this.deviceInterface.removeAllDatabaseEntries(this.identifier)
await this.deviceInterface.removeRawStorageValuesForIdentifier(this.identifier)
@@ -82,42 +111,42 @@ export class DiskStorageService extends Services.AbstractService implements Serv
}
}
public isEphemeralSession() {
return this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral
public isEphemeralSession(): boolean {
return this.persistencePolicy === StoragePersistencePolicies.Ephemeral
}
public async initializeFromDisk() {
public async initializeFromDisk(): Promise<void> {
const value = await this.deviceInterface.getRawStorageValue(this.getPersistenceKey())
const values = value ? JSON.parse(value as string) : undefined
await this.setInitialValues(values)
}
private async setInitialValues(values?: Services.StorageValuesObject) {
private async setInitialValues(values?: StorageValuesObject) {
const sureValues = values || this.defaultValuesObject()
if (!sureValues[Services.ValueModesKeys.Unwrapped]) {
sureValues[Services.ValueModesKeys.Unwrapped] = {}
if (!sureValues[ValueModesKeys.Unwrapped]) {
sureValues[ValueModesKeys.Unwrapped] = {}
}
this.values = sureValues
if (!this.isStorageWrapped()) {
this.values[Services.ValueModesKeys.Unwrapped] = {
...(this.values[Services.ValueModesKeys.Wrapped].content as object),
...this.values[Services.ValueModesKeys.Unwrapped],
this.values[ValueModesKeys.Unwrapped] = {
...(this.values[ValueModesKeys.Wrapped].content as object),
...this.values[ValueModesKeys.Unwrapped],
}
}
}
public isStorageWrapped(): boolean {
const wrappedValue = this.values[Services.ValueModesKeys.Wrapped]
const wrappedValue = this.values[ValueModesKeys.Wrapped]
return wrappedValue != undefined && isEncryptedLocalStoragePayload(wrappedValue)
}
public async canDecryptWithKey(key: SNRootKey): Promise<boolean> {
const wrappedValue = this.values[Services.ValueModesKeys.Wrapped]
const wrappedValue = this.values[ValueModesKeys.Wrapped]
if (!isEncryptedLocalStoragePayload(wrappedValue)) {
throw Error('Attempting to decrypt non decrypted storage value')
@@ -143,7 +172,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
content_type: ContentType.TYPES.EncryptedStorage,
})
const split: Encryption.KeyedDecryptionSplit = key
const split: KeyedDecryptionSplit = key
? {
usesRootKey: {
items: [payload],
@@ -161,8 +190,8 @@ export class DiskStorageService extends Services.AbstractService implements Serv
return decryptedPayload
}
public async decryptStorage() {
const wrappedValue = this.values[Services.ValueModesKeys.Wrapped]
public async decryptStorage(): Promise<void> {
const wrappedValue = this.values[ValueModesKeys.Wrapped]
if (!isEncryptedLocalStoragePayload(wrappedValue)) {
throw Error('Attempting to decrypt already decrypted storage')
@@ -174,7 +203,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
throw SNLog.error(Error('Unable to decrypt storage.'))
}
this.values[Services.ValueModesKeys.Unwrapped] = Copy(decryptedPayload.content)
this.values[ValueModesKeys.Unwrapped] = Copy(decryptedPayload.content)
}
/** @todo This function should be debounced. */
@@ -184,7 +213,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
return
}
if (this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral) {
if (this.persistencePolicy === StoragePersistencePolicies.Ephemeral) {
return
}
@@ -195,18 +224,18 @@ export class DiskStorageService extends Services.AbstractService implements Serv
const values = await this.immediatelyPersistValuesToDisk()
/** Save the persisted value so we have access to it in memory (for unit tests afawk) */
this.values[Services.ValueModesKeys.Wrapped] = values[Services.ValueModesKeys.Wrapped]
this.values[ValueModesKeys.Wrapped] = values[ValueModesKeys.Wrapped]
}
public async awaitPersist(): Promise<void> {
await this.currentPersistPromise
}
private async immediatelyPersistValuesToDisk(): Promise<Services.StorageValuesObject> {
private async immediatelyPersistValuesToDisk(): Promise<StorageValuesObject> {
this.currentPersistPromise = this.executeCriticalFunction(async () => {
const values = await this.generatePersistableValues()
const persistencePolicySuddenlyChanged = this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral
const persistencePolicySuddenlyChanged = this.persistencePolicy === StoragePersistencePolicies.Ephemeral
if (persistencePolicySuddenlyChanged) {
return values
}
@@ -224,10 +253,10 @@ export class DiskStorageService extends Services.AbstractService implements Serv
* either as a plain object, or an encrypted item.
*/
private async generatePersistableValues() {
const rawContent = <Partial<Services.StorageValuesObject>>Copy(this.values)
const rawContent = <Partial<StorageValuesObject>>Copy(this.values)
const valuesToWrap = rawContent[Services.ValueModesKeys.Unwrapped]
rawContent[Services.ValueModesKeys.Unwrapped] = undefined
const valuesToWrap = rawContent[ValueModesKeys.Unwrapped]
rawContent[ValueModesKeys.Unwrapped] = undefined
const payload = new DecryptedPayload({
uuid: UuidGenerator.GenerateUuid(),
@@ -237,7 +266,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
})
if (this.encryptionProvider.hasRootKeyEncryptionSource()) {
const split: Encryption.KeyedEncryptionSplit = {
const split: KeyedEncryptionSplit = {
usesRootKeyWithKeyLookup: {
items: [payload],
},
@@ -245,31 +274,27 @@ export class DiskStorageService extends Services.AbstractService implements Serv
const encryptedPayload = await this.encryptionProvider.encryptSplitSingle(split)
rawContent[Services.ValueModesKeys.Wrapped] = CreateEncryptedLocalStorageContextPayload(encryptedPayload)
rawContent[ValueModesKeys.Wrapped] = CreateEncryptedLocalStorageContextPayload(encryptedPayload)
} else {
rawContent[Services.ValueModesKeys.Wrapped] = CreateDecryptedLocalStorageContextPayload(payload)
rawContent[ValueModesKeys.Wrapped] = CreateDecryptedLocalStorageContextPayload(payload)
}
return rawContent as Services.StorageValuesObject
return rawContent as StorageValuesObject
}
public setValue<T>(key: string, value: T, mode = Services.StorageValueModes.Default): void {
public setValue<T>(key: string, value: T, mode = StorageValueModes.Default): void {
this.setValueWithNoPersist(key, value, mode)
void this.persistValuesToDisk()
}
public async setValueAndAwaitPersist(
key: string,
value: unknown,
mode = Services.StorageValueModes.Default,
): Promise<void> {
public async setValueAndAwaitPersist(key: string, value: unknown, mode = StorageValueModes.Default): Promise<void> {
this.setValueWithNoPersist(key, value, mode)
await this.persistValuesToDisk()
}
private setValueWithNoPersist(key: string, value: unknown, mode = Services.StorageValueModes.Default): void {
private setValueWithNoPersist(key: string, value: unknown, mode = StorageValueModes.Default): void {
if (!this.values) {
throw Error(`Attempting to set storage key ${key} before loading local storage.`)
}
@@ -279,7 +304,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
domainStorage[key] = value
}
public getValue<T>(key: string, mode = Services.StorageValueModes.Default, defaultValue?: T): T {
public getValue<T>(key: string, mode = StorageValueModes.Default, defaultValue?: T): T {
if (!this.values) {
throw Error(`Attempting to get storage key ${key} before loading local storage.`)
}
@@ -293,7 +318,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
return value != undefined ? (value as T) : (defaultValue as T)
}
public getAllKeys(mode = Services.StorageValueModes.Default): string[] {
public getAllKeys(mode = StorageValueModes.Default): string[] {
if (!this.values) {
throw Error('Attempting to get all keys before loading local storage.')
}
@@ -301,7 +326,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
return Object.keys(this.values[this.domainKeyForMode(mode)])
}
public async removeValue(key: string, mode = Services.StorageValueModes.Default): Promise<void> {
public async removeValue(key: string, mode = StorageValueModes.Default): Promise<void> {
if (!this.values) {
throw Error(`Attempting to remove storage key ${key} before loading local storage.`)
}
@@ -318,34 +343,34 @@ export class DiskStorageService extends Services.AbstractService implements Serv
* Default persistence key. Platforms can override as needed.
*/
private getPersistenceKey() {
return Services.namespacedKey(this.identifier, Services.RawStorageKey.StorageObject)
return namespacedKey(this.identifier, RawStorageKey.StorageObject)
}
private defaultValuesObject(
wrapped?: Services.WrappedStorageValue,
unwrapped?: Services.ValuesObjectRecord,
nonwrapped?: Services.ValuesObjectRecord,
wrapped?: WrappedStorageValue,
unwrapped?: ValuesObjectRecord,
nonwrapped?: ValuesObjectRecord,
) {
return DiskStorageService.DefaultValuesObject(wrapped, unwrapped, nonwrapped)
}
public static DefaultValuesObject(
wrapped: Services.WrappedStorageValue = {} as Services.WrappedStorageValue,
unwrapped: Services.ValuesObjectRecord = {},
nonwrapped: Services.ValuesObjectRecord = {},
wrapped: WrappedStorageValue = {} as WrappedStorageValue,
unwrapped: ValuesObjectRecord = {},
nonwrapped: ValuesObjectRecord = {},
) {
return {
[Services.ValueModesKeys.Wrapped]: wrapped,
[Services.ValueModesKeys.Unwrapped]: unwrapped,
[Services.ValueModesKeys.Nonwrapped]: nonwrapped,
} as Services.StorageValuesObject
[ValueModesKeys.Wrapped]: wrapped,
[ValueModesKeys.Unwrapped]: unwrapped,
[ValueModesKeys.Nonwrapped]: nonwrapped,
} as StorageValuesObject
}
private domainKeyForMode(mode: Services.StorageValueModes) {
if (mode === Services.StorageValueModes.Default) {
return Services.ValueModesKeys.Unwrapped
} else if (mode === Services.StorageValueModes.Nonwrapped) {
return Services.ValueModesKeys.Nonwrapped
private domainKeyForMode(mode: StorageValueModes) {
if (mode === StorageValueModes.Default) {
return ValueModesKeys.Unwrapped
} else if (mode === StorageValueModes.Nonwrapped) {
return ValueModesKeys.Nonwrapped
} else {
throw Error('Invalid mode')
}
@@ -368,7 +393,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
}
public async savePayloads(payloads: FullyFormedPayloadInterface[]): Promise<void> {
if (this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral) {
if (this.persistencePolicy === StoragePersistencePolicies.Ephemeral) {
return
}
@@ -380,7 +405,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
const unencryptable: DecryptedPayloadInterface[] = []
const { rootKeyEncryption, keySystemRootKeyEncryption, itemsKeyEncryption } =
Encryption.SplitPayloadsByEncryptionType(decrypted)
SplitPayloadsByEncryptionType(decrypted)
if (itemsKeyEncryption) {
extendArray(encryptable, itemsKeyEncryption)
@@ -402,9 +427,9 @@ export class DiskStorageService extends Services.AbstractService implements Serv
await this.deletePayloads(discardable)
}
const encryptableSplit = Encryption.SplitPayloadsByEncryptionType(encryptable)
const encryptableSplit = SplitPayloadsByEncryptionType(encryptable)
const keyLookupSplit = Encryption.CreateEncryptionSplitWithKeyLookup(encryptableSplit)
const keyLookupSplit = CreateEncryptionSplitWithKeyLookup(encryptableSplit)
const encryptedResults = await this.encryptionProvider.encryptSplit(keyLookupSplit)
@@ -449,9 +474,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
await this.clearValues()
await this.clearAllPayloads()
await this.deviceInterface.removeRawStorageValue(
Services.namespacedKey(this.identifier, Services.RawStorageKey.SnjsVersion),
)
await this.deviceInterface.removeRawStorageValue(namespacedKey(this.identifier, RawStorageKey.SnjsVersion))
await this.deviceInterface.removeRawStorageValue(this.getPersistenceKey())
})

View File

@@ -2,7 +2,7 @@ import { ServerSyncPushContextualPayload } from '@standardnotes/models'
import { arrayByDifference, nonSecureRandomIdentifier, subtractFromArray } from '@standardnotes/utils'
import { ServerSyncResponse } from '@Lib/Services/Sync/Account/Response'
import { ResponseSignalReceiver, SyncSignal } from '@Lib/Services/Sync/Signals'
import { SNApiService } from '../../Api/ApiService'
import { LegacyApiService } from '../../Api/ApiService'
export const SyncUpDownLimit = 150
@@ -23,7 +23,7 @@ export class AccountSyncOperation {
constructor(
public readonly payloads: ServerSyncPushContextualPayload[],
private receiver: ResponseSignalReceiver<ServerSyncResponse>,
private apiService: SNApiService,
private apiService: LegacyApiService,
public readonly options: {
syncToken?: string
paginationToken?: string

View File

@@ -1,14 +0,0 @@
import { SyncOpStatus } from './SyncOpStatus'
import { AbstractService, SyncEvent, SyncOptions } from '@standardnotes/services'
export interface SyncClientInterface extends AbstractService<SyncEvent> {
setLaunchPriorityUuids(launchPriorityUuids: string[]): void
sync(options?: Partial<SyncOptions>): Promise<unknown>
isOutOfSync(): boolean
getLastSyncDate(): Date | undefined
getSyncStatus(): SyncOpStatus
lockSyncing(): void
unlockSyncing(): void
completedOnlineDownloadFirstSync: boolean
}

View File

@@ -1,125 +0,0 @@
import { SyncEvent, SyncEventReceiver } from '@standardnotes/services'
const HEALTHY_SYNC_DURATION_THRESHOLD_S = 5
const TIMING_MONITOR_POLL_FREQUENCY_MS = 500
export class SyncOpStatus {
error?: any
private interval: any
private receiver: SyncEventReceiver
private completedUpload = 0
private totalUpload = 0
private downloaded = 0
private databaseLoadCurrent = 0
private databaseLoadTotal = 0
private databaseLoadDone = false
private syncing = false
private syncStart!: Date
private timingMonitor?: any
constructor(interval: any, receiver: SyncEventReceiver) {
this.interval = interval
this.receiver = receiver
}
public deinit() {
this.stopTimingMonitor()
}
public setUploadStatus(completed: number, total: number) {
this.completedUpload = completed
this.totalUpload = total
this.receiver(SyncEvent.StatusChanged)
}
public setDownloadStatus(downloaded: number) {
this.downloaded += downloaded
this.receiver(SyncEvent.StatusChanged)
}
public setDatabaseLoadStatus(current: number, total: number, done: boolean) {
this.databaseLoadCurrent = current
this.databaseLoadTotal = total
this.databaseLoadDone = done
if (done) {
this.receiver(SyncEvent.LocalDataLoaded)
} else {
this.receiver(SyncEvent.LocalDataIncrementalLoad)
}
}
public getStats() {
return {
uploadCompletionCount: this.completedUpload,
uploadTotalCount: this.totalUpload,
downloadCount: this.downloaded,
localDataDone: this.databaseLoadDone,
localDataCurrent: this.databaseLoadCurrent,
localDataTotal: this.databaseLoadTotal,
}
}
public setDidBegin() {
this.syncing = true
this.syncStart = new Date()
}
public setDidEnd() {
this.syncing = false
}
get syncInProgress() {
return this.syncing === true
}
get secondsSinceSyncStart() {
return (new Date().getTime() - this.syncStart.getTime()) / 1000
}
/**
* Notifies receiver if current sync request is taking too long to complete.
*/
startTimingMonitor(): void {
if (this.timingMonitor) {
this.stopTimingMonitor()
}
this.timingMonitor = this.interval(() => {
if (this.secondsSinceSyncStart > HEALTHY_SYNC_DURATION_THRESHOLD_S) {
this.receiver(SyncEvent.SyncTakingTooLong)
this.stopTimingMonitor()
}
}, TIMING_MONITOR_POLL_FREQUENCY_MS)
}
stopTimingMonitor(): void {
if (Object.prototype.hasOwnProperty.call(this.interval, 'cancel')) {
this.interval.cancel(this.timingMonitor)
} else {
clearInterval(this.timingMonitor)
}
this.timingMonitor = null
}
hasError(): boolean {
return !!this.error
}
setError(error: any): void {
this.error = error
}
clearError() {
this.error = null
}
reset() {
this.downloaded = 0
this.completedUpload = 0
this.totalUpload = 0
this.syncing = false
this.error = null
this.stopTimingMonitor()
this.receiver(SyncEvent.StatusChanged)
}
}

View File

@@ -13,14 +13,12 @@ import {
import { ItemManager } from '@Lib/Services/Items/ItemManager'
import { OfflineSyncOperation } from '@Lib/Services/Sync/Offline/Operation'
import { PayloadManager } from '../Payloads/PayloadManager'
import { SNApiService } from '../Api/ApiService'
import { SNHistoryManager } from '../History/HistoryManager'
import { LegacyApiService } from '../Api/ApiService'
import { HistoryManager } from '../History/HistoryManager'
import { SNLog } from '@Lib/Log'
import { SNSessionManager } from '../Session/SessionManager'
import { SessionManager } from '../Session/SessionManager'
import { DiskStorageService } from '../Storage/DiskStorageService'
import { SyncClientInterface } from './SyncClientInterface'
import { SyncPromise } from './Types'
import { SyncOpStatus } from '@Lib/Services/Sync/SyncOpStatus'
import { ServerSyncResponse } from '@Lib/Services/Sync/Account/Response'
import { ServerSyncResponseResolver } from '@Lib/Services/Sync/Account/ResponseResolver'
import { SyncSignal, SyncStats } from '@Lib/Services/Sync/Signals'
@@ -84,6 +82,7 @@ import {
SyncEventReceivedRemoteSharedVaultsData,
SyncEventReceivedUserEventsData,
SyncEventReceivedAsymmetricMessagesData,
SyncOpStatus,
} from '@standardnotes/services'
import { OfflineSyncResponse } from './Offline/Response'
import {
@@ -121,9 +120,9 @@ const ContentTypeLocalLoadPriorty = [
* After each sync request, any changes made or retrieved are also persisted locally.
* The sync service largely does not perform any task unless it is called upon.
*/
export class SNSyncService
export class SyncService
extends AbstractService<SyncEvent>
implements SyncServiceInterface, InternalEventHandlerInterface, SyncClientInterface
implements SyncServiceInterface, InternalEventHandlerInterface
{
private dirtyIndexAtLastPresyncSave?: number
private lastSyncDate?: Date
@@ -152,12 +151,12 @@ export class SNSyncService
constructor(
private itemManager: ItemManager,
private sessionManager: SNSessionManager,
private sessionManager: SessionManager,
private encryptionService: EncryptionService,
private storageService: DiskStorageService,
private payloadManager: PayloadManager,
private apiService: SNApiService,
private historyService: SNHistoryManager,
private apiService: LegacyApiService,
private historyService: HistoryManager,
private device: DeviceInterface,
private identifier: string,
private readonly options: ApplicationSyncOptions,
@@ -968,25 +967,25 @@ export class SNSyncService
const historyMap = this.historyService.getHistoryMapCopy()
if (response.userEvents) {
if (response.userEvents && response.userEvents.length > 0) {
await this.notifyEventSync(SyncEvent.ReceivedUserEvents, response.userEvents as SyncEventReceivedUserEventsData)
}
if (response.asymmetricMessages) {
if (response.asymmetricMessages && response.asymmetricMessages.length > 0) {
await this.notifyEventSync(
SyncEvent.ReceivedAsymmetricMessages,
response.asymmetricMessages as SyncEventReceivedAsymmetricMessagesData,
)
}
if (response.vaults) {
if (response.vaults && response.vaults.length > 0) {
await this.notifyEventSync(
SyncEvent.ReceivedRemoteSharedVaults,
response.vaults as SyncEventReceivedRemoteSharedVaultsData,
)
}
if (response.vaultInvites) {
if (response.vaultInvites && response.vaultInvites.length > 0) {
await this.notifyEventSync(
SyncEvent.ReceivedSharedVaultInvites,
response.vaultInvites as SyncEventReceivedSharedVaultInvitesData,

View File

@@ -1,7 +1,5 @@
export * from './SyncService'
export * from './Types'
export * from './SyncOpStatus'
export * from './SyncClientInterface'
export * from './Account/Operation'
export * from './Account/ResponseResolver'
export * from './Offline/Operation'

View File

@@ -1,4 +1,5 @@
export * from './Application'
export * from './Application/Dependencies/Types'
export * from './ApplicationGroup'
export * from './Client'
export * from './Domain'