feat(web): extract ui-services package

This commit is contained in:
Karol Sójko
2022-08-04 15:13:30 +02:00
parent c72a407095
commit 7e251262d7
161 changed files with 1105 additions and 824 deletions

View File

@@ -19,9 +19,12 @@ import * as Settings from '@standardnotes/settings'
import * as Files from '@standardnotes/files'
import { Subscription } from '@standardnotes/security'
import { UuidString, ApplicationEventPayload } from '../Types'
import { ApplicationEvent, applicationEventForSyncEvent } from '@Lib/Application/Event'
import { applicationEventForSyncEvent } from '@Lib/Application/Event'
import {
ApplicationEvent,
ApplicationEventCallback,
ChallengeValidation,
ComponentManagerInterface,
DiagnosticInfo,
Environment,
isDesktopDevice,
@@ -36,7 +39,7 @@ import {
} from '@standardnotes/services'
import { SNLog } from '../Log'
import { useBoolean } from '@standardnotes/utils'
import { DecryptedItemInterface, EncryptedItemInterface } from '@standardnotes/models'
import { BackupFile, DecryptedItemInterface, EncryptedItemInterface, ItemStream } from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { Challenge, ChallengeResponse } from '../Services'
import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions'
@@ -49,20 +52,11 @@ type LaunchCallback = {
receiveChallenge: (challenge: Challenge) => void
}
type ApplicationEventCallback = (event: ApplicationEvent, data?: unknown) => Promise<void>
type ApplicationObserver = {
singleEvent?: ApplicationEvent
callback: ApplicationEventCallback
}
type ItemStream<I extends DecryptedItemInterface> = (data: {
changed: I[]
inserted: I[]
removed: (Models.DeletedItemInterface | Models.EncryptedItemInterface)[]
source: Models.PayloadEmitSource
}) => void
type ObserverRemover = () => void
export class SNApplication
@@ -97,7 +91,7 @@ export class SNApplication
private syncService!: InternalServices.SNSyncService
private challengeService!: InternalServices.ChallengeService
public singletonManager!: InternalServices.SNSingletonManager
public componentManager!: InternalServices.SNComponentManager
public componentManagerService!: InternalServices.SNComponentManager
public protectionService!: InternalServices.SNProtectionService
public actionsManager!: InternalServices.SNActionsService
public historyManager!: InternalServices.SNHistoryManager
@@ -192,11 +186,11 @@ export class SNApplication
return this.fileService
}
public get features(): InternalServices.FeaturesClientInterface {
public get features(): ExternalServices.FeaturesClientInterface {
return this.featuresService
}
public get items(): InternalServices.ItemsClientInterface {
public get items(): ExternalServices.ItemsClientInterface {
return this.itemManager
}
@@ -216,7 +210,7 @@ export class SNApplication
return this.settingsService
}
public get mutator(): InternalServices.MutatorClientInterface {
public get mutator(): ExternalServices.MutatorClientInterface {
return this.mutatorService
}
@@ -232,6 +226,10 @@ export class SNApplication
return this.filesBackupService
}
public get componentManager(): ComponentManagerInterface {
return this.componentManagerService
}
public computePrivateWorkspaceIdentifier(userphrase: string, name: string): Promise<string | undefined> {
return Encryption.ComputePrivateWorkspaceIdentifier(this.options.crypto, userphrase, name)
}
@@ -659,11 +657,11 @@ export class SNApplication
return this.listedService.getListedAccountInfo(account, inContextOfItem)
}
public async createEncryptedBackupFileForAutomatedDesktopBackups(): Promise<Encryption.BackupFile | undefined> {
public async createEncryptedBackupFileForAutomatedDesktopBackups(): Promise<BackupFile | undefined> {
return this.protocolService.createEncryptedBackupFile()
}
public async createEncryptedBackupFile(): Promise<Encryption.BackupFile | undefined> {
public async createEncryptedBackupFile(): Promise<BackupFile | undefined> {
if (!(await this.protectionService.authorizeBackupCreation())) {
return
}
@@ -671,7 +669,7 @@ export class SNApplication
return this.protocolService.createEncryptedBackupFile()
}
public async createDecryptedBackupFile(): Promise<Encryption.BackupFile | undefined> {
public async createDecryptedBackupFile(): Promise<BackupFile | undefined> {
if (!(await this.protectionService.authorizeBackupCreation())) {
return
}
@@ -1070,7 +1068,7 @@ export class SNApplication
;(this.syncService as unknown) = undefined
;(this.challengeService as unknown) = undefined
;(this.singletonManager as unknown) = undefined
;(this.componentManager as unknown) = undefined
;(this.componentManagerService as unknown) = undefined
;(this.protectionService as unknown) = undefined
;(this.actionsManager as unknown) = undefined
;(this.historyManager as unknown) = undefined
@@ -1161,11 +1159,11 @@ export class SNApplication
this.serviceObservers.push(
this.featuresService.addEventObserver((event) => {
switch (event) {
case InternalServices.FeaturesEvent.UserRolesChanged: {
case ExternalServices.FeaturesEvent.UserRolesChanged: {
void this.notifyEvent(ApplicationEvent.UserRolesChanged)
break
}
case InternalServices.FeaturesEvent.FeaturesUpdated: {
case ExternalServices.FeaturesEvent.FeaturesUpdated: {
void this.notifyEvent(ApplicationEvent.FeaturesUpdated)
break
}
@@ -1268,7 +1266,7 @@ export class SNApplication
const MaybeSwappedComponentManager = this.getClass<typeof InternalServices.SNComponentManager>(
InternalServices.SNComponentManager,
)
this.componentManager = new MaybeSwappedComponentManager(
this.componentManagerService = new MaybeSwappedComponentManager(
this.itemManager,
this.syncService,
this.featuresService,
@@ -1278,7 +1276,7 @@ export class SNApplication
this.platform,
this.internalEventBus,
)
this.services.push(this.componentManager)
this.services.push(this.componentManagerService)
}
private createHttpManager() {
@@ -1533,7 +1531,7 @@ export class SNApplication
this.protocolService,
this.payloadManager,
this.challengeService,
this.componentManager,
this.componentManagerService,
this.historyManager,
this.internalEventBus,
)

View File

@@ -1,72 +1,6 @@
import { SyncEvent } from '@standardnotes/services'
import { ApplicationEvent, SyncEvent } from '@standardnotes/services'
export { SyncEvent }
export enum ApplicationEvent {
SignedIn = 2,
SignedOut = 3,
/** When a full, potentially multi-page sync completes */
CompletedFullSync = 5,
FailedSync = 6,
HighLatencySync = 7,
EnteredOutOfSync = 8,
ExitedOutOfSync = 9,
/**
* The application has finished it `prepareForLaunch` state and is now ready for unlock
* Called when the application has initialized and is ready for launch, but before
* the application has been unlocked, if applicable. Use this to do pre-launch
* configuration, but do not attempt to access user data like notes or tags.
*/
Started = 10,
/**
* The applicaiton is fully unlocked and ready for i/o
* Called when the application has been fully decrypted and unlocked. Use this to
* to begin streaming data like notes and tags.
*/
Launched = 11,
LocalDataLoaded = 12,
/**
* When the root key or root key wrapper changes. Includes events like account state
* changes (registering, signing in, changing pw, logging out) and passcode state
* changes (adding, removing, changing).
*/
KeyStatusChanged = 13,
MajorDataChange = 14,
CompletedRestart = 15,
LocalDataIncrementalLoad = 16,
SyncStatusChanged = 17,
WillSync = 18,
InvalidSyncSession = 19,
LocalDatabaseReadError = 20,
LocalDatabaseWriteError = 21,
/** When a single roundtrip completes with sync, in a potentially multi-page sync request.
* If just a single roundtrip, this event will be triggered, along with CompletedFullSync */
CompletedIncrementalSync = 22,
/**
* The application has loaded all pending migrations (but not run any, except for the base one),
* and consumers may now call `hasPendingMigrations`
*/
MigrationsLoaded = 23,
/** When StorageService is ready to start servicing read/write requests */
StorageReady = 24,
PreferencesChanged = 25,
UnprotectedSessionBegan = 26,
UserRolesChanged = 27,
FeaturesUpdated = 28,
UnprotectedSessionExpired = 29,
/** Called when the app first launches and after first sync request made after sign in */
CompletedInitialSync = 30,
}
export function applicationEventForSyncEvent(syncEvent: SyncEvent) {
return (
{

View File

@@ -1,7 +1,9 @@
import { ApplicationEvent } from '../Application/Event'
import { FileItem, PrefKey, SNNote } from '@standardnotes/models'
import { removeFromArray } from '@standardnotes/utils'
import { ApplicationEvent } from '@standardnotes/services'
import { SNApplication } from '../Application/Application'
import { NoteViewController } from './NoteViewController'
import { FileViewController } from './FileViewController'
import { TemplateNoteViewControllerOptions } from './TemplateNoteViewControllerOptions'

View File

@@ -1,11 +1,17 @@
import { ApplicationEvent } from '@Lib/Application/Event'
import { AbstractService, InternalEventBusInterface } from '@standardnotes/services'
import { SNApplication } from '../../Application/Application'
import {
AbstractService,
ApplicationEvent,
ApplicationInterface,
InternalEventBusInterface,
} from '@standardnotes/services'
export class ApplicationService extends AbstractService {
private unsubApp!: () => void
constructor(protected application: SNApplication, protected override internalEventBus: InternalEventBusInterface) {
constructor(
protected application: ApplicationInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
this.addAppEventObserverAfterSubclassesFinishConstructing()
}

View File

@@ -10,10 +10,15 @@ import {
FindNativeFeature,
FeatureIdentifier,
} from '@standardnotes/features'
import { DesktopManagerInterface } from '@Lib/Services/ComponentManager/Types'
import { ContentType } from '@standardnotes/common'
import { GenericItem, SNComponent } from '@standardnotes/models'
import { InternalEventBusInterface, Environment, Platform, AlertService } from '@standardnotes/services'
import {
DesktopManagerInterface,
InternalEventBusInterface,
Environment,
Platform,
AlertService,
} from '@standardnotes/services'
import { ItemManager } from '@Lib/Services/Items/ItemManager'
import { SNFeaturesService } from '@Lib/Services/Features/FeaturesService'
import { SNComponentManager } from './ComponentManager'
@@ -34,7 +39,10 @@ describe('featuresService', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
syncComponentsInstallation() {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
registerUpdateObserver() {},
registerUpdateObserver(_callback: (component: SNComponent) => void) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => {}
},
getExtServerHost() {
return desktopExtHost
},

View File

@@ -3,21 +3,28 @@ import { SNPreferencesService } from '../Preferences/PreferencesService'
import { SNFeaturesService } from '@Lib/Services/Features/FeaturesService'
import { ContentType, DisplayStringForContentType } from '@standardnotes/common'
import { ItemManager } from '@Lib/Services/Items/ItemManager'
import { SNNote, SNTheme, SNComponent, ComponentMutator, PayloadEmitSource } from '@standardnotes/models'
import {
ActionObserver,
SNNote,
SNTheme,
SNComponent,
ComponentMutator,
PayloadEmitSource,
PermissionDialog,
} from '@standardnotes/models'
import { SNSyncService } from '@Lib/Services/Sync/SyncService'
import find from 'lodash/find'
import uniq from 'lodash/uniq'
import { ComponentArea, ComponentAction, ComponentPermission, FindNativeFeature } from '@standardnotes/features'
import { Copy, filterFromArray, removeFromArray, sleep, assert } from '@standardnotes/utils'
import { UuidString } from '@Lib/Types/UuidString'
import {
PermissionDialog,
DesktopManagerInterface,
AllowedBatchContentTypes,
} from '@Lib/Services/ComponentManager/Types'
import { ActionObserver, ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
import { AllowedBatchContentTypes } from '@Lib/Services/ComponentManager/Types'
import { ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
import {
AbstractService,
ComponentManagerInterface,
ComponentViewerInterface,
DesktopManagerInterface,
InternalEventBusInterface,
Environment,
Platform,
@@ -42,7 +49,7 @@ export enum ComponentManagerEvent {
}
export type EventData = {
componentViewer?: ComponentViewer
componentViewer?: ComponentViewerInterface
}
/**
@@ -50,9 +57,12 @@ export type EventData = {
* and other components. The component manager primarily deals with iframes, and orchestrates
* sending and receiving messages to and from frames via the postMessage API.
*/
export class SNComponentManager extends AbstractService<ComponentManagerEvent, EventData> {
export class SNComponentManager
extends AbstractService<ComponentManagerEvent, EventData>
implements ComponentManagerInterface
{
private desktopManager?: DesktopManagerInterface
private viewers: ComponentViewer[] = []
private viewers: ComponentViewerInterface[] = []
private removeItemObserver!: () => void
private permissionDialogs: PermissionDialog[] = []
@@ -137,7 +147,7 @@ export class SNComponentManager extends AbstractService<ComponentManagerEvent, E
contextItem?: UuidString,
actionObserver?: ActionObserver,
urlOverride?: string,
): ComponentViewer {
): ComponentViewerInterface {
const viewer = new ComponentViewer(
component,
this.itemManager,
@@ -159,7 +169,7 @@ export class SNComponentManager extends AbstractService<ComponentManagerEvent, E
return viewer
}
public destroyComponentViewer(viewer: ComponentViewer): void {
public destroyComponentViewer(viewer: ComponentViewerInterface): void {
viewer.destroy()
removeFromArray(this.viewers, viewer)
}
@@ -316,11 +326,11 @@ export class SNComponentManager extends AbstractService<ComponentManagerEvent, E
return this.itemManager.findItem<SNComponent>(uuid)
}
findComponentViewer(identifier: string): ComponentViewer | undefined {
findComponentViewer(identifier: string): ComponentViewerInterface | undefined {
return this.viewers.find((viewer) => viewer.identifier === identifier)
}
componentViewerForSessionKey(key: string): ComponentViewer | undefined {
componentViewerForSessionKey(key: string): ComponentViewerInterface | undefined {
return this.viewers.find((viewer) => viewer.sessionKey === key)
}

View File

@@ -1,8 +1,19 @@
import { SNPreferencesService } from '../Preferences/PreferencesService'
import { FeatureStatus, FeaturesEvent } from '@Lib/Services/Features'
import { Environment, Platform, AlertService } from '@standardnotes/services'
import {
ComponentViewerInterface,
ComponentViewerError,
Environment,
FeatureStatus,
FeaturesEvent,
Platform,
AlertService,
} from '@standardnotes/services'
import { SNFeaturesService } from '@Lib/Services'
import {
ActionObserver,
ComponentEventObserver,
ComponentViewerEvent,
ComponentMessage,
SNComponent,
PrefKey,
NoteContent,
@@ -21,6 +32,8 @@ import {
ComponentDataDomain,
PayloadEmitSource,
PayloadTimestampDefaults,
IncomingComponentItemPayload,
MessageData,
} from '@standardnotes/models'
import find from 'lodash/find'
import uniq from 'lodash/uniq'
@@ -28,12 +41,10 @@ import remove from 'lodash/remove'
import { SNSyncService } from '@Lib/Services/Sync/SyncService'
import { environmentToString, platformToString } from '@Lib/Application/Platforms'
import {
ComponentMessage,
OutgoingItemMessagePayload,
MessageReply,
StreamItemsMessageData,
AllowedBatchContentTypes,
IncomingComponentItemPayload,
DeleteItemsMessageData,
MessageReplyData,
} from './Types'
@@ -53,7 +64,6 @@ import {
sureSearchArray,
isNotUndefined,
} from '@standardnotes/utils'
import { MessageData } from '..'
type RunWithPermissionsCallback = (
componentUuid: UuidString,
@@ -76,21 +86,9 @@ const ReadwriteActions = [
ComponentAction.SetComponentData,
]
export type ActionObserver = (action: ComponentAction, messageData: MessageData) => void
export enum ComponentViewerEvent {
FeatureStatusUpdated = 'FeatureStatusUpdated',
}
type EventObserver = (event: ComponentViewerEvent) => void
export enum ComponentViewerError {
OfflineRestricted = 'OfflineRestricted',
MissingUrl = 'MissingUrl',
}
type Writeable<T> = { -readonly [P in keyof T]: T[P] }
export class ComponentViewer {
export class ComponentViewer implements ComponentViewerInterface {
private streamItems?: ContentType[]
private streamContextItemOriginalMessage?: ComponentMessage
private streamItemsOriginalMessage?: ComponentMessage
@@ -101,7 +99,7 @@ export class ComponentViewer {
public overrideContextItem?: DecryptedItemInterface
private featureStatus: FeatureStatus
private removeFeaturesObserver: () => void
private eventObservers: EventObserver[] = []
private eventObservers: ComponentEventObserver[] = []
private dealloced = false
private window?: Window
@@ -189,7 +187,7 @@ export class ComponentViewer {
;(this.removeItemObserver as unknown) = undefined
}
public addEventObserver(observer: EventObserver): () => void {
public addEventObserver(observer: ComponentEventObserver): () => void {
this.eventObservers.push(observer)
const thislessChangeObservers = this.eventObservers

View File

@@ -1,24 +1,8 @@
import {
ComponentArea,
ComponentAction,
ComponentPermission,
FeatureIdentifier,
LegacyFileSafeIdentifier,
} from '@standardnotes/features'
import { ItemContent, SNComponent, DecryptedTransferPayload } from '@standardnotes/models'
import { ComponentArea, ComponentAction, FeatureIdentifier, LegacyFileSafeIdentifier } from '@standardnotes/features'
import { ComponentMessage, ItemContent, MessageData } from '@standardnotes/models'
import { UuidString } from '@Lib/Types/UuidString'
import { ContentType } from '@standardnotes/common'
export interface DesktopManagerInterface {
syncComponentsInstallation(components: SNComponent[]): void
registerUpdateObserver(callback: (component: SNComponent) => void): void
getExtServerHost(): string
}
export type IncomingComponentItemPayload = DecryptedTransferPayload & {
clientData: Record<string, unknown>
}
export type OutgoingItemMessagePayload = {
uuid: string
content_type: ContentType
@@ -63,46 +47,6 @@ export type StreamObserver = {
contentTypes?: ContentType[]
}
export type PermissionDialog = {
component: SNComponent
permissions: ComponentPermission[]
permissionsString: string
actionBlock: (approved: boolean) => void
callback: (approved: boolean) => void
}
export enum KeyboardModifier {
Shift = 'Shift',
Ctrl = 'Control',
Meta = 'Meta',
}
export type MessageData = Partial<{
/** Related to the stream-item-context action */
item?: IncomingComponentItemPayload
/** Related to the stream-items action */
content_types?: ContentType[]
items?: IncomingComponentItemPayload[]
/** Related to the request-permission action */
permissions?: ComponentPermission[]
/** Related to the component-registered action */
componentData?: Record<string, unknown>
uuid?: UuidString
environment?: string
platform?: string
activeThemeUrls?: string[]
/** Related to set-size action */
width?: string | number
height?: string | number
type?: string
/** Related to themes action */
themes?: string[]
/** Related to clear-selection action */
content_type?: ContentType
/** Related to key-pressed action */
keyboardModifier?: KeyboardModifier
}>
export type MessageReplyData = {
approved?: boolean
deleted?: boolean
@@ -120,13 +64,6 @@ export type DeleteItemsMessageData = MessageData & {
items: OutgoingItemMessagePayload[]
}
export type ComponentMessage = {
action: ComponentAction
sessionKey?: string
componentData?: Record<string, unknown>
data: MessageData
}
export type MessageReply = {
action: ComponentAction
original: ComponentMessage

View File

@@ -1,36 +0,0 @@
import { FeatureStatus, SetOfflineFeaturesFunctionResponse } from './Types'
import { FeatureDescription, FeatureIdentifier } from '@standardnotes/features'
import { SNComponent } from '@standardnotes/models'
import { RoleName } from '@standardnotes/common'
export interface FeaturesClientInterface {
downloadExternalFeature(urlOrCode: string): Promise<SNComponent | undefined>
getUserFeature(featureId: FeatureIdentifier): FeatureDescription | undefined
getFeatureStatus(featureId: FeatureIdentifier): FeatureStatus
hasMinimumRole(role: RoleName): boolean
setOfflineFeaturesCode(code: string): Promise<SetOfflineFeaturesFunctionResponse>
hasOfflineRepo(): boolean
deleteOfflineFeatureRepo(): Promise<void>
isThirdPartyFeature(identifier: string): boolean
toggleExperimentalFeature(identifier: FeatureIdentifier): void
getExperimentalFeatures(): FeatureIdentifier[]
getEnabledExperimentalFeatures(): FeatureIdentifier[]
enableExperimentalFeature(identifier: FeatureIdentifier): void
disableExperimentalFeature(identifier: FeatureIdentifier): void
isExperimentalFeatureEnabled(identifier: FeatureIdentifier): boolean
isExperimentalFeature(identifier: FeatureIdentifier): boolean
}

View File

@@ -10,14 +10,14 @@ import {
DiskStorageService,
StorageKey,
} from '@Lib/index'
import { FeatureStatus, SNFeaturesService } from '@Lib/Services/Features'
import { SNFeaturesService } from '@Lib/Services/Features'
import { ContentType, RoleName } from '@standardnotes/common'
import { FeatureDescription, FeatureIdentifier, GetFeatures } from '@standardnotes/features'
import { SNWebSocketsService } from '../Api/WebsocketsService'
import { SNSettingsService } from '../Settings'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { convertTimestampToMilliseconds } from '@standardnotes/utils'
import { InternalEventBusInterface } from '@standardnotes/services'
import { FeatureStatus, InternalEventBusInterface } from '@standardnotes/services'
describe('featuresService', () => {
let storageService: DiskStorageService

View File

@@ -10,7 +10,6 @@ import {
} from '@standardnotes/utils'
import { ClientDisplayableError, UserFeaturesResponse } from '@standardnotes/responses'
import { ContentType, RoleName } from '@standardnotes/common'
import { FeaturesClientInterface } from './ClientInterface'
import { FillItemContent, PayloadEmitSource } from '@standardnotes/models'
import { ItemManager } from '../Items/ItemManager'
import { LEGACY_PROD_EXT_ORIGIN, PROD_OFFLINE_FEATURES_URL } from '../../Hosts'
@@ -27,20 +26,30 @@ import { UuidString } from '@Lib/Types/UuidString'
import * as FeaturesImports from '@standardnotes/features'
import * as Messages from '@Lib/Services/Api/Messages'
import * as Models from '@standardnotes/models'
import * as Services from '@standardnotes/services'
import {
AbstractService,
AlertService,
ApiServiceEvent,
ApplicationStage,
ButtonType,
DiagnosticInfo,
FeaturesClientInterface,
FeaturesEvent,
FeatureStatus,
InternalEventBusInterface,
InternalEventHandlerInterface,
InternalEventInterface,
MetaReceivedData,
OfflineSubscriptionEntitlements,
SetOfflineFeaturesFunctionResponse,
} from './Types'
import { DiagnosticInfo } from '@standardnotes/services'
StorageKey,
} from '@standardnotes/services'
type GetOfflineSubscriptionDetailsResponse = OfflineSubscriptionEntitlements | ClientDisplayableError
export class SNFeaturesService
extends Services.AbstractService<FeaturesEvent>
implements FeaturesClientInterface, Services.InternalEventHandlerInterface
extends AbstractService<FeaturesEvent>
implements FeaturesClientInterface, InternalEventHandlerInterface
{
private deinited = false
private roles: RoleName[] = []
@@ -60,10 +69,10 @@ export class SNFeaturesService
private settingsService: SNSettingsService,
private userService: UserService,
private syncService: SNSyncService,
private alertService: Services.AlertService,
private alertService: AlertService,
private sessionManager: SNSessionManager,
private crypto: PureCryptoInterface,
protected override internalEventBus: Services.InternalEventBusInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -109,8 +118,8 @@ export class SNFeaturesService
})
}
async handleEvent(event: Services.InternalEventInterface): Promise<void> {
if (event.type === Services.ApiServiceEvent.MetaReceived) {
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApiServiceEvent.MetaReceived) {
if (!this.syncService) {
this.log('[Features Service] Handling events interrupted. Sync service is not yet initialized.', event)
@@ -126,7 +135,7 @@ export class SNFeaturesService
return
}
const { userUuid, userRoles } = event.payload as Services.MetaReceivedData
const { userUuid, userRoles } = event.payload as MetaReceivedData
await this.updateRolesAndFetchFeatures(
userUuid,
userRoles.map((role) => role.name),
@@ -134,9 +143,9 @@ export class SNFeaturesService
}
}
override async handleApplicationStage(stage: Services.ApplicationStage): Promise<void> {
override async handleApplicationStage(stage: ApplicationStage): Promise<void> {
await super.handleApplicationStage(stage)
if (stage === Services.ApplicationStage.FullSyncCompleted_13) {
if (stage === ApplicationStage.FullSyncCompleted_13) {
if (!this.hasOnlineSubscription()) {
const offlineRepo = this.getOfflineRepo()
if (offlineRepo) {
@@ -154,7 +163,7 @@ export class SNFeaturesService
this.enabledExperimentalFeatures.push(identifier)
void this.storageService.setValue(Services.StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
void this.storageService.setValue(StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
void this.mapRemoteNativeFeaturesToItems([feature])
void this.notifyEvent(FeaturesEvent.FeaturesUpdated)
@@ -168,7 +177,7 @@ export class SNFeaturesService
removeFromArray(this.enabledExperimentalFeatures, identifier)
void this.storageService.setValue(Services.StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
void this.storageService.setValue(StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
const component = this.itemManager
.getItems<Models.SNComponent | Models.SNTheme>([ContentType.Component, ContentType.Theme])
@@ -248,7 +257,7 @@ export class SNFeaturesService
await this.itemManager.setItemToBeDeleted(repo)
void this.syncService.sync()
}
await this.storageService.removeValue(Services.StorageKey.UserFeatures)
await this.storageService.removeValue(StorageKey.UserFeatures)
}
private parseOfflineEntitlementsCode(code: string): GetOfflineSubscriptionDetailsResponse | ClientDisplayableError {
@@ -322,15 +331,11 @@ export class SNFeaturesService
}
public initializeFromDisk(): void {
this.roles = this.storageService.getValue<RoleName[]>(Services.StorageKey.UserRoles, undefined, [])
this.roles = this.storageService.getValue<RoleName[]>(StorageKey.UserRoles, undefined, [])
this.features = this.storageService.getValue(Services.StorageKey.UserFeatures, undefined, [])
this.features = this.storageService.getValue(StorageKey.UserFeatures, undefined, [])
this.enabledExperimentalFeatures = this.storageService.getValue(
Services.StorageKey.ExperimentalFeatures,
undefined,
[],
)
this.enabledExperimentalFeatures = this.storageService.getValue(StorageKey.ExperimentalFeatures, undefined, [])
}
public async updateRolesAndFetchFeatures(userUuid: UuidString, roles: RoleName[]): Promise<void> {
@@ -361,7 +366,7 @@ export class SNFeaturesService
if (!arraysEqual(this.roles, roles)) {
void this.notifyEvent(FeaturesEvent.UserRolesChanged)
}
await this.storageService.setValue(Services.StorageKey.UserRoles, this.roles)
await this.storageService.setValue(StorageKey.UserRoles, this.roles)
}
public async didDownloadFeatures(features: FeaturesImports.FeatureDescription[]): Promise<void> {
@@ -372,7 +377,7 @@ export class SNFeaturesService
this.features = features
this.completedSuccessfulFeaturesRetrieval = true
void this.notifyEvent(FeaturesEvent.FeaturesUpdated)
void this.storageService.setValue(Services.StorageKey.UserFeatures, this.features)
void this.storageService.setValue(StorageKey.UserFeatures, this.features)
await this.mapRemoteNativeFeaturesToItems(features)
}
@@ -607,7 +612,7 @@ export class SNFeaturesService
Messages.API_MESSAGE_UNTRUSTED_EXTENSIONS_WARNING,
'Install extension from an untrusted source?',
'Proceed to install',
Services.ButtonType.Danger,
ButtonType.Danger,
'Cancel',
)
if (didConfirm) {

View File

@@ -1,20 +0,0 @@
import { ClientDisplayableError } from '@standardnotes/responses'
export type SetOfflineFeaturesFunctionResponse = ClientDisplayableError | undefined
export type OfflineSubscriptionEntitlements = {
featuresUrl: string
extensionKey: string
}
export enum FeaturesEvent {
UserRolesChanged = 'UserRolesChanged',
FeaturesUpdated = 'FeaturesUpdated',
}
export enum FeatureStatus {
NoUserSubscription = 'NoUserSubscription',
NotInCurrentPlan = 'NotInCurrentPlan',
InCurrentPlanButExpired = 'InCurrentPlanButExpired',
Entitled = 'Entitled',
}

View File

@@ -1,3 +1 @@
export * from './ClientInterface'
export * from './FeaturesService'
export * from './Types'

View File

@@ -3,13 +3,11 @@ import { assert, naturalSort, removeFromArray, UuidGenerator, Uuids } from '@sta
import { ItemsKeyMutator, SNItemsKey } from '@standardnotes/encryption'
import { PayloadManager } from '../Payloads/PayloadManager'
import { TagsToFoldersMigrationApplicator } from '../../Migrations/Applicators/TagsToFolders'
import { TransactionalMutation } from './TransactionalMutation'
import { UuidString } from '../../Types/UuidString'
import * as Models from '@standardnotes/models'
import * as Services from '@standardnotes/services'
import { ItemsClientInterface } from './ItemsClientInterface'
import { PayloadManagerChangeData } from '../Payloads'
import { DiagnosticInfo } from '@standardnotes/services'
import { DiagnosticInfo, ItemsClientInterface } from '@standardnotes/services'
import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions'
import { CollectionSort } from '@standardnotes/models'
@@ -578,7 +576,7 @@ export class ItemManager
* runs the same mutation on all items.
*/
public async runTransactionalMutations(
transactions: TransactionalMutation[],
transactions: Models.TransactionalMutation[],
emitSource = Models.PayloadEmitSource.LocalChanged,
payloadSourceKey?: string,
): Promise<(Models.DecryptedItemInterface | undefined)[]> {
@@ -607,7 +605,7 @@ export class ItemManager
}
public async runTransactionalMutation(
transaction: TransactionalMutation,
transaction: Models.TransactionalMutation,
emitSource = Models.PayloadEmitSource.LocalChanged,
payloadSourceKey?: string,
): Promise<Models.DecryptedItemInterface | undefined> {

View File

@@ -1,141 +0,0 @@
import { SNItemsKey } from '@standardnotes/encryption'
import { ContentType } from '@standardnotes/common'
import {
SNNote,
FileItem,
SNTag,
SmartView,
TagNoteCountChangeObserver,
DecryptedPayloadInterface,
EncryptedItemInterface,
DecryptedTransferPayload,
PredicateInterface,
DecryptedItemInterface,
SNComponent,
SNTheme,
DisplayOptions,
} from '@standardnotes/models'
import { UuidString } from '@Lib/Types'
export interface ItemsClientInterface {
get invalidItems(): EncryptedItemInterface[]
associateFileWithNote(file: FileItem, note: SNNote): Promise<FileItem>
disassociateFileWithNote(file: FileItem, note: SNNote): Promise<FileItem>
getFilesForNote(note: SNNote): FileItem[]
renameFile(file: FileItem, name: string): Promise<FileItem>
addTagToNote(note: SNNote, tag: SNTag, addHierarchy: boolean): Promise<SNTag[]>
/** Creates an unmanaged, un-inserted item from a payload. */
createItemFromPayload(payload: DecryptedPayloadInterface): DecryptedItemInterface
createPayloadFromObject(object: DecryptedTransferPayload): DecryptedPayloadInterface
get trashedItems(): SNNote[]
setPrimaryItemDisplayOptions(options: DisplayOptions): void
getDisplayableNotes(): SNNote[]
getDisplayableTags(): SNTag[]
getDisplayableItemsKeys(): SNItemsKey[]
getDisplayableFiles(): FileItem[]
getDisplayableNotesAndFiles(): (SNNote | FileItem)[]
getDisplayableComponents(): (SNComponent | SNTheme)[]
getItems<T extends DecryptedItemInterface>(contentType: ContentType | ContentType[]): T[]
notesMatchingSmartView(view: SmartView): SNNote[]
addNoteCountChangeObserver(observer: TagNoteCountChangeObserver): () => void
allCountableNotesCount(): number
countableNotesForTag(tag: SNTag | SmartView): number
findTagByTitle(title: string): SNTag | undefined
getTagPrefixTitle(tag: SNTag): string | undefined
getTagLongTitle(tag: SNTag): string
hasTagsNeedingFoldersMigration(): boolean
referencesForItem(itemToLookupUuidFor: DecryptedItemInterface, contentType?: ContentType): DecryptedItemInterface[]
itemsReferencingItem(itemToLookupUuidFor: DecryptedItemInterface, contentType?: ContentType): DecryptedItemInterface[]
/**
* Finds tags with title or component starting with a search query and (optionally) not associated with a note
* @param searchQuery - The query string to match
* @param note - The note whose tags should be omitted from results
* @returns Array containing tags matching search query and not associated with note
*/
searchTags(searchQuery: string, note?: SNNote): SNTag[]
isValidTagParent(parentTagToLookUpUuidFor: SNTag, childToLookUpUuidFor: SNTag): boolean
/**
* Returns the parent for a tag
*/
getTagParent(itemToLookupUuidFor: SNTag): SNTag | undefined
/**
* Returns the hierarchy of parents for a tag
* @returns Array containing all parent tags
*/
getTagParentChain(itemToLookupUuidFor: SNTag): SNTag[]
/**
* Returns all descendants for a tag
* @returns Array containing all descendant tags
*/
getTagChildren(itemToLookupUuidFor: SNTag): SNTag[]
/**
* Get tags for a note sorted in natural order
* @param note - The note whose tags will be returned
* @returns Array containing tags associated with a note
*/
getSortedTagsForNote(note: SNNote): SNTag[]
isSmartViewTitle(title: string): boolean
getSmartViews(): SmartView[]
getNoteCount(): number
/**
* Finds an item by UUID.
*/
findItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: UuidString): T | undefined
/**
* Finds an item by predicate.
*/
findItems<T extends DecryptedItemInterface>(uuids: UuidString[]): T[]
findSureItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: UuidString): T
/**
* Finds an item by predicate.
*/
itemsMatchingPredicate<T extends DecryptedItemInterface>(
contentType: ContentType,
predicate: PredicateInterface<T>,
): T[]
/**
* @param item item to be checked
* @returns Whether the item is a template (unmanaged)
*/
isTemplateItem(item: DecryptedItemInterface): boolean
}

View File

@@ -1,8 +0,0 @@
import * as Models from '@standardnotes/models'
import { UuidString } from '../../Types/UuidString'
export type TransactionalMutation = {
itemUuid: UuidString
mutate: (mutator: Models.ItemMutator) => void
mutationType?: Models.MutationType
}

View File

@@ -1,3 +1 @@
export * from './ItemsClientInterface'
export * from './ItemManager'
export * from './TransactionalMutation'

View File

@@ -1,4 +1,3 @@
import { ApplicationEvent } from '../../Application/Event'
import { BaseMigration } from '@Lib/Migrations/Base'
import { compareSemVersions } from '@Lib/Version'
import { lastElement } from '@standardnotes/utils'
@@ -7,6 +6,7 @@ import { MigrationServices } from '../../Migrations/MigrationServices'
import {
RawStorageKey,
namespacedKey,
ApplicationEvent,
ApplicationStage,
AbstractService,
DiagnosticInfo,

View File

@@ -1,183 +0,0 @@
import { ContentType } from '@standardnotes/common'
import { ChallengeReason, SyncOptions } from '@standardnotes/services'
import { TransactionalMutation } from '../Items'
import * as Models from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { BackupFile } from '@standardnotes/encryption'
export interface MutatorClientInterface {
/**
* Inserts the input item by its payload properties, and marks the item as dirty.
* A sync is not performed after an item is inserted. This must be handled by the caller.
*/
insertItem(item: Models.DecryptedItemInterface): Promise<Models.DecryptedItemInterface>
/**
* Mutates a pre-existing item, marks it as dirty, and syncs it
*/
changeAndSaveItem<M extends Models.DecryptedItemMutator = Models.DecryptedItemMutator>(
itemToLookupUuidFor: Models.DecryptedItemInterface,
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
emitSource?: Models.PayloadEmitSource,
syncOptions?: SyncOptions,
): Promise<Models.DecryptedItemInterface | undefined>
/**
* Mutates pre-existing items, marks them as dirty, and syncs
*/
changeAndSaveItems<M extends Models.DecryptedItemMutator = Models.DecryptedItemMutator>(
itemsToLookupUuidsFor: Models.DecryptedItemInterface[],
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
emitSource?: Models.PayloadEmitSource,
syncOptions?: SyncOptions,
): Promise<void>
/**
* Mutates a pre-existing item and marks it as dirty. Does not sync changes.
*/
changeItem<M extends Models.DecryptedItemMutator>(
itemToLookupUuidFor: Models.DecryptedItemInterface,
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
): Promise<Models.DecryptedItemInterface | undefined>
/**
* Mutates a pre-existing items and marks them as dirty. Does not sync changes.
*/
changeItems<M extends Models.DecryptedItemMutator = Models.DecryptedItemMutator>(
itemsToLookupUuidsFor: Models.DecryptedItemInterface[],
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
): Promise<(Models.DecryptedItemInterface | undefined)[]>
/**
* Run unique mutations per each item in the array, then only propagate all changes
* once all mutations have been run. This differs from `changeItems` in that changeItems
* runs the same mutation on all items.
*/
runTransactionalMutations(
transactions: TransactionalMutation[],
emitSource?: Models.PayloadEmitSource,
payloadSourceKey?: string,
): Promise<(Models.DecryptedItemInterface | undefined)[]>
runTransactionalMutation(
transaction: TransactionalMutation,
emitSource?: Models.PayloadEmitSource,
payloadSourceKey?: string,
): Promise<Models.DecryptedItemInterface | undefined>
protectItems<
_M extends Models.DecryptedItemMutator<Models.ItemContent>,
I extends Models.DecryptedItemInterface<Models.ItemContent>,
>(
items: I[],
): Promise<I[]>
unprotectItems<
_M extends Models.DecryptedItemMutator<Models.ItemContent>,
I extends Models.DecryptedItemInterface<Models.ItemContent>,
>(
items: I[],
reason: ChallengeReason,
): Promise<I[] | undefined>
protectNote(note: Models.SNNote): Promise<Models.SNNote>
unprotectNote(note: Models.SNNote): Promise<Models.SNNote | undefined>
protectNotes(notes: Models.SNNote[]): Promise<Models.SNNote[]>
unprotectNotes(notes: Models.SNNote[]): Promise<Models.SNNote[]>
protectFile(file: Models.FileItem): Promise<Models.FileItem>
unprotectFile(file: Models.FileItem): Promise<Models.FileItem | undefined>
/**
* Takes the values of the input item and emits it onto global state.
*/
mergeItem(
item: Models.DecryptedItemInterface,
source: Models.PayloadEmitSource,
): Promise<Models.DecryptedItemInterface>
/**
* Creates an unmanaged item that can be added later.
*/
createTemplateItem<
C extends Models.ItemContent = Models.ItemContent,
I extends Models.DecryptedItemInterface<C> = Models.DecryptedItemInterface<C>,
>(
contentType: ContentType,
content?: C,
): I
/**
* @param isUserModified Whether to change the modified date the user
* sees of the item.
*/
setItemNeedsSync(
item: Models.DecryptedItemInterface,
isUserModified?: boolean,
): Promise<Models.DecryptedItemInterface | undefined>
setItemsNeedsSync(items: Models.DecryptedItemInterface[]): Promise<(Models.DecryptedItemInterface | undefined)[]>
deleteItem(item: Models.DecryptedItemInterface | Models.EncryptedItemInterface): Promise<void>
deleteItems(items: (Models.DecryptedItemInterface | Models.EncryptedItemInterface)[]): Promise<void>
emptyTrash(): Promise<void>
duplicateItem<T extends Models.DecryptedItemInterface>(item: T, additionalContent?: Partial<T['content']>): Promise<T>
/**
* Migrates any tags containing a '.' character to sa chema-based heirarchy, removing
* the dot from the tag's title.
*/
migrateTagsToFolders(): Promise<unknown>
/**
* Establishes a hierarchical relationship between two tags.
*/
setTagParent(parentTag: Models.SNTag, childTag: Models.SNTag): Promise<void>
/**
* Remove the tag parent.
*/
unsetTagParent(childTag: Models.SNTag): Promise<void>
findOrCreateTag(title: string): Promise<Models.SNTag>
/** Creates and returns the tag but does not run sync. Callers must perform sync. */
createTagOrSmartView(title: string): Promise<Models.SNTag | Models.SmartView>
/**
* Activates or deactivates a component, depending on its
* current state, and syncs.
*/
toggleComponent(component: Models.SNComponent): Promise<void>
toggleTheme(theme: Models.SNComponent): Promise<void>
/**
* @returns
* .affectedItems: Items that were either created or dirtied by this import
* .errorCount: The number of items that were not imported due to failure to decrypt.
*/
importData(
data: BackupFile,
awaitSync?: boolean,
): Promise<
| {
affectedItems: Models.DecryptedItemInterface[]
errorCount: number
}
| {
error: ClientDisplayableError
}
>
}

View File

@@ -6,25 +6,42 @@ import {
ChallengeValidation,
ChallengePrompt,
ChallengeReason,
MutatorClientInterface,
} from '@standardnotes/services'
import { BackupFile, EncryptionProvider } from '@standardnotes/encryption'
import { EncryptionProvider } from '@standardnotes/encryption'
import { ClientDisplayableError } from '@standardnotes/responses'
import { ContentType, ProtocolVersion, compareVersions } from '@standardnotes/common'
import { ItemManager, TransactionalMutation } from '../Items'
import { MutatorClientInterface } from './MutatorClientInterface'
import { ItemManager } from '../Items'
import { PayloadManager } from '../Payloads/PayloadManager'
import { SNComponentManager } from '../ComponentManager/ComponentManager'
import { SNProtectionService } from '../Protection/ProtectionService'
import { SNSyncService } from '../Sync'
import { Strings } from '../../Strings'
import { TagsToFoldersMigrationApplicator } from '@Lib/Migrations/Applicators/TagsToFolders'
import * as Models from '@standardnotes/models'
import { Challenge, ChallengeService } from '../Challenge'
import {
BackupFile,
BackupFileDecryptedContextualPayload,
ComponentContent,
CopyPayloadWithContentOverride,
CreateDecryptedBackupFileContextPayload,
CreateDecryptedMutatorForItem,
CreateEncryptedBackupFileContextPayload,
DecryptedItemInterface,
DecryptedItemMutator,
DecryptedPayloadInterface,
EncryptedItemInterface,
FileItem,
isDecryptedPayload,
isEncryptedTransferPayload,
ItemContent,
MutationType,
PayloadEmitSource,
SmartView,
SNComponent,
SNNote,
SNTag,
TransactionalMutation,
} from '@standardnotes/models'
export class MutatorService extends AbstractService implements MutatorClientInterface {
@@ -54,106 +71,101 @@ export class MutatorService extends AbstractService implements MutatorClientInte
;(this.historyService as unknown) = undefined
}
public async insertItem(item: Models.DecryptedItemInterface): Promise<Models.DecryptedItemInterface> {
const mutator = Models.CreateDecryptedMutatorForItem(item, Models.MutationType.UpdateUserTimestamps)
public async insertItem(item: DecryptedItemInterface): Promise<DecryptedItemInterface> {
const mutator = CreateDecryptedMutatorForItem(item, MutationType.UpdateUserTimestamps)
const dirtiedPayload = mutator.getResult()
const insertedItem = await this.itemManager.emitItemFromPayload(
dirtiedPayload,
Models.PayloadEmitSource.LocalInserted,
)
const insertedItem = await this.itemManager.emitItemFromPayload(dirtiedPayload, PayloadEmitSource.LocalInserted)
return insertedItem
}
public async changeAndSaveItem<M extends Models.DecryptedItemMutator = Models.DecryptedItemMutator>(
itemToLookupUuidFor: Models.DecryptedItemInterface,
public async changeAndSaveItem<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemToLookupUuidFor: DecryptedItemInterface,
mutate: (mutator: M) => void,
updateTimestamps = true,
emitSource?: Models.PayloadEmitSource,
emitSource?: PayloadEmitSource,
syncOptions?: SyncOptions,
): Promise<Models.DecryptedItemInterface | undefined> {
): Promise<DecryptedItemInterface | undefined> {
await this.itemManager.changeItems(
[itemToLookupUuidFor],
mutate,
updateTimestamps ? Models.MutationType.UpdateUserTimestamps : Models.MutationType.NoUpdateUserTimestamps,
updateTimestamps ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps,
emitSource,
)
await this.syncService.sync(syncOptions)
return this.itemManager.findItem(itemToLookupUuidFor.uuid)
}
public async changeAndSaveItems<M extends Models.DecryptedItemMutator = Models.DecryptedItemMutator>(
itemsToLookupUuidsFor: Models.DecryptedItemInterface[],
public async changeAndSaveItems<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemsToLookupUuidsFor: DecryptedItemInterface[],
mutate: (mutator: M) => void,
updateTimestamps = true,
emitSource?: Models.PayloadEmitSource,
emitSource?: PayloadEmitSource,
syncOptions?: SyncOptions,
): Promise<void> {
await this.itemManager.changeItems(
itemsToLookupUuidsFor,
mutate,
updateTimestamps ? Models.MutationType.UpdateUserTimestamps : Models.MutationType.NoUpdateUserTimestamps,
updateTimestamps ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps,
emitSource,
)
await this.syncService.sync(syncOptions)
}
public async changeItem<M extends Models.DecryptedItemMutator>(
itemToLookupUuidFor: Models.DecryptedItemInterface,
public async changeItem<M extends DecryptedItemMutator>(
itemToLookupUuidFor: DecryptedItemInterface,
mutate: (mutator: M) => void,
updateTimestamps = true,
): Promise<Models.DecryptedItemInterface | undefined> {
): Promise<DecryptedItemInterface | undefined> {
await this.itemManager.changeItems(
[itemToLookupUuidFor],
mutate,
updateTimestamps ? Models.MutationType.UpdateUserTimestamps : Models.MutationType.NoUpdateUserTimestamps,
updateTimestamps ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps,
)
return this.itemManager.findItem(itemToLookupUuidFor.uuid)
}
public async changeItems<M extends Models.DecryptedItemMutator = Models.DecryptedItemMutator>(
itemsToLookupUuidsFor: Models.DecryptedItemInterface[],
public async changeItems<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemsToLookupUuidsFor: DecryptedItemInterface[],
mutate: (mutator: M) => void,
updateTimestamps = true,
): Promise<(Models.DecryptedItemInterface | undefined)[]> {
): Promise<(DecryptedItemInterface | undefined)[]> {
return this.itemManager.changeItems(
itemsToLookupUuidsFor,
mutate,
updateTimestamps ? Models.MutationType.UpdateUserTimestamps : Models.MutationType.NoUpdateUserTimestamps,
updateTimestamps ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps,
)
}
public async runTransactionalMutations(
transactions: TransactionalMutation[],
emitSource = Models.PayloadEmitSource.LocalChanged,
emitSource = PayloadEmitSource.LocalChanged,
payloadSourceKey?: string,
): Promise<(Models.DecryptedItemInterface | undefined)[]> {
): Promise<(DecryptedItemInterface | undefined)[]> {
return this.itemManager.runTransactionalMutations(transactions, emitSource, payloadSourceKey)
}
public async runTransactionalMutation(
transaction: TransactionalMutation,
emitSource = Models.PayloadEmitSource.LocalChanged,
emitSource = PayloadEmitSource.LocalChanged,
payloadSourceKey?: string,
): Promise<Models.DecryptedItemInterface | undefined> {
): Promise<DecryptedItemInterface | undefined> {
return this.itemManager.runTransactionalMutation(transaction, emitSource, payloadSourceKey)
}
async protectItems<M extends Models.DecryptedItemMutator, I extends Models.DecryptedItemInterface>(
items: I[],
): Promise<I[]> {
async protectItems<M extends DecryptedItemMutator, I extends DecryptedItemInterface>(items: I[]): Promise<I[]> {
const protectedItems = await this.itemManager.changeItems<M, I>(
items,
(mutator) => {
mutator.protected = true
},
Models.MutationType.NoUpdateUserTimestamps,
MutationType.NoUpdateUserTimestamps,
)
void this.syncService.sync()
return protectedItems
}
async unprotectItems<M extends Models.DecryptedItemMutator, I extends Models.DecryptedItemInterface>(
async unprotectItems<M extends DecryptedItemMutator, I extends DecryptedItemInterface>(
items: I[],
reason: ChallengeReason,
): Promise<I[] | undefined> {
@@ -166,74 +178,69 @@ export class MutatorService extends AbstractService implements MutatorClientInte
(mutator) => {
mutator.protected = false
},
Models.MutationType.NoUpdateUserTimestamps,
MutationType.NoUpdateUserTimestamps,
)
void this.syncService.sync()
return unprotectedItems
}
public async protectNote(note: Models.SNNote): Promise<Models.SNNote> {
public async protectNote(note: SNNote): Promise<SNNote> {
const result = await this.protectItems([note])
return result[0]
}
public async unprotectNote(note: Models.SNNote): Promise<Models.SNNote | undefined> {
public async unprotectNote(note: SNNote): Promise<SNNote | undefined> {
const result = await this.unprotectItems([note], ChallengeReason.UnprotectNote)
return result ? result[0] : undefined
}
public async protectNotes(notes: Models.SNNote[]): Promise<Models.SNNote[]> {
public async protectNotes(notes: SNNote[]): Promise<SNNote[]> {
return this.protectItems(notes)
}
public async unprotectNotes(notes: Models.SNNote[]): Promise<Models.SNNote[]> {
public async unprotectNotes(notes: SNNote[]): Promise<SNNote[]> {
const results = await this.unprotectItems(notes, ChallengeReason.UnprotectNote)
return results || []
}
async protectFile(file: Models.FileItem): Promise<Models.FileItem> {
async protectFile(file: FileItem): Promise<FileItem> {
const result = await this.protectItems([file])
return result[0]
}
async unprotectFile(file: Models.FileItem): Promise<Models.FileItem | undefined> {
async unprotectFile(file: FileItem): Promise<FileItem | undefined> {
const result = await this.unprotectItems([file], ChallengeReason.UnprotectFile)
return result ? result[0] : undefined
}
public async mergeItem(
item: Models.DecryptedItemInterface,
source: Models.PayloadEmitSource,
): Promise<Models.DecryptedItemInterface> {
public async mergeItem(item: DecryptedItemInterface, source: PayloadEmitSource): Promise<DecryptedItemInterface> {
return this.itemManager.emitItemFromPayload(item.payloadRepresentation(), source)
}
public createTemplateItem<
C extends Models.ItemContent = Models.ItemContent,
I extends Models.DecryptedItemInterface<C> = Models.DecryptedItemInterface<C>,
C extends ItemContent = ItemContent,
I extends DecryptedItemInterface<C> = DecryptedItemInterface<C>,
>(contentType: ContentType, content?: C): I {
return this.itemManager.createTemplateItem(contentType, content)
}
public async setItemNeedsSync(
item: Models.DecryptedItemInterface,
item: DecryptedItemInterface,
updateTimestamps = false,
): Promise<Models.DecryptedItemInterface | undefined> {
): Promise<DecryptedItemInterface | undefined> {
return this.itemManager.setItemDirty(item, updateTimestamps)
}
public async setItemsNeedsSync(
items: Models.DecryptedItemInterface[],
): Promise<(Models.DecryptedItemInterface | undefined)[]> {
public async setItemsNeedsSync(items: DecryptedItemInterface[]): Promise<(DecryptedItemInterface | undefined)[]> {
return this.itemManager.setItemsDirty(items)
}
public async deleteItem(item: Models.DecryptedItemInterface | Models.EncryptedItemInterface): Promise<void> {
public async deleteItem(item: DecryptedItemInterface | EncryptedItemInterface): Promise<void> {
return this.deleteItems([item])
}
public async deleteItems(items: (Models.DecryptedItemInterface | Models.EncryptedItemInterface)[]): Promise<void> {
public async deleteItems(items: (DecryptedItemInterface | EncryptedItemInterface)[]): Promise<void> {
await this.itemManager.setItemsToBeDeleted(items)
await this.syncService.sync()
}
@@ -243,7 +250,7 @@ export class MutatorService extends AbstractService implements MutatorClientInte
await this.syncService.sync()
}
public duplicateItem<T extends Models.DecryptedItemInterface>(
public duplicateItem<T extends DecryptedItemInterface>(
item: T,
additionalContent?: Partial<T['content']>,
): Promise<T> {
@@ -257,29 +264,29 @@ export class MutatorService extends AbstractService implements MutatorClientInte
return this.syncService.sync()
}
public async setTagParent(parentTag: Models.SNTag, childTag: Models.SNTag): Promise<void> {
public async setTagParent(parentTag: SNTag, childTag: SNTag): Promise<void> {
await this.itemManager.setTagParent(parentTag, childTag)
}
public async unsetTagParent(childTag: Models.SNTag): Promise<void> {
public async unsetTagParent(childTag: SNTag): Promise<void> {
await this.itemManager.unsetTagParent(childTag)
}
public async findOrCreateTag(title: string): Promise<Models.SNTag> {
public async findOrCreateTag(title: string): Promise<SNTag> {
return this.itemManager.findOrCreateTagByTitle(title)
}
/** Creates and returns the tag but does not run sync. Callers must perform sync. */
public async createTagOrSmartView(title: string): Promise<Models.SNTag | Models.SmartView> {
public async createTagOrSmartView(title: string): Promise<SNTag | SmartView> {
return this.itemManager.createTagOrSmartView(title)
}
public async toggleComponent(component: Models.SNComponent): Promise<void> {
public async toggleComponent(component: SNComponent): Promise<void> {
await this.componentManager.toggleComponent(component.uuid)
await this.syncService.sync()
}
public async toggleTheme(theme: Models.SNComponent): Promise<void> {
public async toggleTheme(theme: SNComponent): Promise<void> {
await this.componentManager.toggleTheme(theme.uuid)
await this.syncService.sync()
}
@@ -289,7 +296,7 @@ export class MutatorService extends AbstractService implements MutatorClientInte
awaitSync = false,
): Promise<
| {
affectedItems: Models.DecryptedItemInterface[]
affectedItems: DecryptedItemInterface[]
errorCount: number
}
| {
@@ -342,7 +349,7 @@ export class MutatorService extends AbstractService implements MutatorClientInte
if (isEncryptedTransferPayload(item)) {
return CreateEncryptedBackupFileContextPayload(item)
} else {
return CreateDecryptedBackupFileContextPayload(item as Models.BackupFileDecryptedContextualPayload)
return CreateDecryptedBackupFileContextPayload(item as BackupFileDecryptedContextualPayload)
}
})
@@ -355,9 +362,9 @@ export class MutatorService extends AbstractService implements MutatorClientInte
const validPayloads = decryptedPayloadsOrError.filter(isDecryptedPayload).map((payload) => {
/* Don't want to activate any components during import process in
* case of exceptions breaking up the import proccess */
if (payload.content_type === ContentType.Component && (payload.content as Models.ComponentContent).active) {
const typedContent = payload as Models.DecryptedPayloadInterface<Models.ComponentContent>
return Models.CopyPayloadWithContentOverride(typedContent, {
if (payload.content_type === ContentType.Component && (payload.content as ComponentContent).active) {
const typedContent = payload as DecryptedPayloadInterface<ComponentContent>
return CopyPayloadWithContentOverride(typedContent, {
active: false,
})
} else {
@@ -376,7 +383,7 @@ export class MutatorService extends AbstractService implements MutatorClientInte
await promise
}
const affectedItems = this.itemManager.findItems(affectedUuids) as Models.DecryptedItemInterface[]
const affectedItems = this.itemManager.findItems(affectedUuids) as DecryptedItemInterface[]
return {
affectedItems: affectedItems,

View File

@@ -1,2 +1 @@
export * from './MutatorClientInterface'
export * from './MutatorService'