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

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'