refactor: application dependency management (#2363)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user