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
No known key found for this signature in database
GPG key ID: A50543BF560BDEB0
161 changed files with 1105 additions and 824 deletions

View file

@ -28,6 +28,7 @@
"build:desktop": "yarn workspaces foreach -pt --verbose -R --from @standardnotes/desktop --exclude @standardnotes/components-meta run build",
"build:mobile": "yarn workspaces foreach -pt --verbose -R --from @standardnotes/mobile --exclude @standardnotes/components-meta run build",
"build:snjs": "yarn workspaces foreach -pt --verbose -R --from @standardnotes/snjs --exclude @standardnotes/components-meta run build",
"build:services": "yarn workspaces foreach -pt --verbose -R --from @standardnotes/services --exclude @standardnotes/components-meta run build",
"start:server:web": "lerna run start --scope=@standardnotes/web",
"start:server:e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
"prepare": "husky install",

View file

@ -1,9 +0,0 @@
import { AnyKeyParamsContent, ProtocolVersion } from '@standardnotes/common'
import { BackupFileDecryptedContextualPayload, BackupFileEncryptedContextualPayload } from '@standardnotes/models'
export type BackupFile = {
version?: ProtocolVersion
keyParams?: AnyKeyParamsContent
auth_params?: AnyKeyParamsContent
items: (BackupFileDecryptedContextualPayload | BackupFileEncryptedContextualPayload)[]
}

View file

@ -6,6 +6,7 @@ import {
ProtocolVersion,
} from '@standardnotes/common'
import {
BackupFile,
CreateDecryptedItemFromPayload,
CreatePayloadSplit,
DecryptedPayload,
@ -28,7 +29,6 @@ import { CreateAnyKeyParams } from '../Keys/RootKey/KeyParamsFunctions'
import { SNRootKey } from '../Keys/RootKey/RootKey'
import { SNRootKeyParams } from '../Keys/RootKey/RootKeyParams'
import { EncryptionService } from '../Service/Encryption/EncryptionService'
import { BackupFile } from './BackupFile'
import { BackupFileType } from './BackupFileType'
export async function DecryptBackupFile(

View file

@ -1,12 +1,12 @@
import { ProtocolVersion } from '@standardnotes/common'
import {
BackupFile,
DecryptedPayloadInterface,
EncryptedPayloadInterface,
ItemContent,
RootKeyInterface,
} from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { BackupFile } from '../../Backups/BackupFile'
import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
import { KeyedDecryptionSplit } from '../../Split/KeyedDecryptionSplit'
import { KeyedEncryptionSplit } from '../../Split/KeyedEncryptionSplit'

View file

@ -1,6 +1,7 @@
import * as Common from '@standardnotes/common'
import * as Models from '@standardnotes/models'
import {
BackupFile,
CreateDecryptedBackupFileContextPayload,
CreateEncryptedBackupFileContextPayload,
EncryptedPayload,
@ -15,7 +16,6 @@ import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import * as Utils from '@standardnotes/utils'
import { isNotUndefined } from '@standardnotes/utils'
import { V001Algorithm, V002Algorithm } from '../../Algorithm'
import { BackupFile } from '../../Backups/BackupFile'
import { DecryptBackupFile } from '../../Backups/BackupFileDecryptor'
import { CreateAnyKeyParams } from '../../Keys/RootKey/KeyParamsFunctions'
import { SNRootKey } from '../../Keys/RootKey/RootKey'

View file

@ -1,5 +1,4 @@
export * from './Algorithm'
export * from './Backups/BackupFile'
export * from './Backups/BackupFileDecryptor'
export * from './Backups/BackupFileType'
export * from './Keys/ItemsKey/ItemsKey'

View file

@ -0,0 +1,4 @@
import { ComponentAction } from '@standardnotes/features'
import { MessageData } from './MessageData'
export type ActionObserver = (action: ComponentAction, messageData: MessageData) => void

View file

@ -0,0 +1,3 @@
import { ComponentViewerEvent } from './ComponentViewerEvent'
export type ComponentEventObserver = (event: ComponentViewerEvent) => void

View file

@ -0,0 +1,9 @@
import { ComponentAction } from '@standardnotes/features'
import { MessageData } from './MessageData'
export type ComponentMessage = {
action: ComponentAction
sessionKey?: string
componentData?: Record<string, unknown>
data: MessageData
}

View file

@ -0,0 +1,3 @@
export enum ComponentViewerEvent {
FeatureStatusUpdated = 'FeatureStatusUpdated',
}

View file

@ -0,0 +1,5 @@
import { DecryptedTransferPayload } from '../TransferPayload/Interfaces/DecryptedTransferPayload'
export type IncomingComponentItemPayload = DecryptedTransferPayload & {
clientData: Record<string, unknown>
}

View file

@ -0,0 +1,5 @@
export enum KeyboardModifier {
Shift = 'Shift',
Ctrl = 'Control',
Meta = 'Meta',
}

View file

@ -0,0 +1,31 @@
import { ContentType, Uuid } from '@standardnotes/common'
import { ComponentPermission } from '@standardnotes/features'
import { IncomingComponentItemPayload } from './IncomingComponentItemPayload'
import { KeyboardModifier } from './KeyboardModifier'
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?: Uuid
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
}>

View file

@ -0,0 +1,10 @@
import { ComponentPermission } from '@standardnotes/features'
import { SNComponent } from '../../Syncable/Component'
export type PermissionDialog = {
component: SNComponent
permissions: ComponentPermission[]
permissionsString: string
actionBlock: (approved: boolean) => void
callback: (approved: boolean) => void
}

View file

@ -1,60 +1,10 @@
import { Uuid } from '@standardnotes/common'
import { ContextPayload } from './ContextPayload'
import { ItemContent } from '../Content/ItemContent'
import { DecryptedTransferPayload, EncryptedTransferPayload } from '../TransferPayload'
import { AnyKeyParamsContent, ProtocolVersion } from '@standardnotes/common'
import { BackupFileDecryptedContextualPayload } from './BackupFileDecryptedContextualPayload'
import { BackupFileEncryptedContextualPayload } from './BackupFileEncryptedContextualPayload'
export interface BackupFileEncryptedContextualPayload extends ContextPayload {
auth_hash?: string
content: string
created_at_timestamp: number
created_at: Date
duplicate_of?: Uuid
enc_item_key: string
items_key_id: string | undefined
updated_at: Date
updated_at_timestamp: number
}
export interface BackupFileDecryptedContextualPayload<C extends ItemContent = ItemContent> extends ContextPayload {
content: C
created_at_timestamp: number
created_at: Date
duplicate_of?: Uuid
updated_at: Date
updated_at_timestamp: number
}
export function CreateEncryptedBackupFileContextPayload(
fromPayload: EncryptedTransferPayload,
): BackupFileEncryptedContextualPayload {
return {
auth_hash: fromPayload.auth_hash,
content_type: fromPayload.content_type,
content: fromPayload.content,
created_at_timestamp: fromPayload.created_at_timestamp,
created_at: fromPayload.created_at,
deleted: false,
duplicate_of: fromPayload.duplicate_of,
enc_item_key: fromPayload.enc_item_key,
items_key_id: fromPayload.items_key_id,
updated_at_timestamp: fromPayload.updated_at_timestamp,
updated_at: fromPayload.updated_at,
uuid: fromPayload.uuid,
}
}
export function CreateDecryptedBackupFileContextPayload(
fromPayload: DecryptedTransferPayload,
): BackupFileDecryptedContextualPayload {
return {
content_type: fromPayload.content_type,
content: fromPayload.content,
created_at_timestamp: fromPayload.created_at_timestamp,
created_at: fromPayload.created_at,
deleted: false,
duplicate_of: fromPayload.duplicate_of,
updated_at_timestamp: fromPayload.updated_at_timestamp,
updated_at: fromPayload.updated_at,
uuid: fromPayload.uuid,
}
export type BackupFile = {
version?: ProtocolVersion
keyParams?: AnyKeyParamsContent
auth_params?: AnyKeyParamsContent
items: (BackupFileDecryptedContextualPayload | BackupFileEncryptedContextualPayload)[]
}

View file

@ -0,0 +1,12 @@
import { Uuid } from '@standardnotes/common'
import { ItemContent } from '../Content/ItemContent'
import { ContextPayload } from './ContextPayload'
export interface BackupFileDecryptedContextualPayload<C extends ItemContent = ItemContent> extends ContextPayload {
content: C
created_at_timestamp: number
created_at: Date
duplicate_of?: Uuid
updated_at: Date
updated_at_timestamp: number
}

View file

@ -0,0 +1,14 @@
import { Uuid } from '@standardnotes/common'
import { ContextPayload } from './ContextPayload'
export interface BackupFileEncryptedContextualPayload extends ContextPayload {
auth_hash?: string
content: string
created_at_timestamp: number
created_at: Date
duplicate_of?: Uuid
enc_item_key: string
items_key_id: string | undefined
updated_at: Date
updated_at_timestamp: number
}

View file

@ -0,0 +1,39 @@
import { DecryptedTransferPayload, EncryptedTransferPayload } from '../TransferPayload'
import { BackupFileDecryptedContextualPayload } from './BackupFileDecryptedContextualPayload'
import { BackupFileEncryptedContextualPayload } from './BackupFileEncryptedContextualPayload'
export function CreateEncryptedBackupFileContextPayload(
fromPayload: EncryptedTransferPayload,
): BackupFileEncryptedContextualPayload {
return {
auth_hash: fromPayload.auth_hash,
content_type: fromPayload.content_type,
content: fromPayload.content,
created_at_timestamp: fromPayload.created_at_timestamp,
created_at: fromPayload.created_at,
deleted: false,
duplicate_of: fromPayload.duplicate_of,
enc_item_key: fromPayload.enc_item_key,
items_key_id: fromPayload.items_key_id,
updated_at_timestamp: fromPayload.updated_at_timestamp,
updated_at: fromPayload.updated_at,
uuid: fromPayload.uuid,
}
}
export function CreateDecryptedBackupFileContextPayload(
fromPayload: DecryptedTransferPayload,
): BackupFileDecryptedContextualPayload {
return {
content_type: fromPayload.content_type,
content: fromPayload.content,
created_at_timestamp: fromPayload.created_at_timestamp,
created_at: fromPayload.created_at,
deleted: false,
duplicate_of: fromPayload.duplicate_of,
updated_at_timestamp: fromPayload.updated_at_timestamp,
updated_at: fromPayload.updated_at,
uuid: fromPayload.uuid,
}
}

View file

@ -1,10 +0,0 @@
export * from './ComponentCreate'
export * from './ComponentRetrieved'
export * from './BackupFile'
export * from './LocalStorage'
export * from './OfflineSyncPush'
export * from './OfflineSyncSaved'
export * from './ServerSyncPush'
export * from './SessionHistory'
export * from './ServerSyncSaved'
export * from './FilteredServerItem'

View file

@ -0,0 +1,11 @@
import { Uuid } from '@standardnotes/common'
import { MutationType } from '../Types/MutationType'
import { ItemMutator } from './ItemMutator'
export type TransactionalMutation = {
itemUuid: Uuid
mutate: (mutator: ItemMutator) => void
mutationType?: MutationType
}

View file

@ -0,0 +1,11 @@
import { PayloadEmitSource } from '../../Payload'
import { DecryptedItemInterface } from '../Interfaces/DecryptedItem'
import { DeletedItemInterface } from '../Interfaces/DeletedItem'
import { EncryptedItemInterface } from '../Interfaces/EncryptedItem'
export type ItemStream<I extends DecryptedItemInterface> = (data: {
changed: I[]
inserted: I[]
removed: (DeletedItemInterface | EncryptedItemInterface)[]
source: PayloadEmitSource
}) => void

View file

@ -20,10 +20,10 @@ export * from './Interfaces/TypeCheck'
export * from './Mutator/DecryptedItemMutator'
export * from './Mutator/DeleteMutator'
export * from './Mutator/ItemMutator'
export * from './Types/AppDataField'
export * from './Mutator/TransactionalMutation'
export * from './Types/AppDataField'
export * from './Types/ConflictStrategy'
export * from './Types/DefaultAppDomain'
export * from './Types/DefaultAppDomain'
export * from './Types/ItemStream'
export * from './Types/MutationType'
export * from './Types/SingletonStrategy'

View file

@ -1,5 +1,26 @@
export * from './Abstract/Component/ActionObserver'
export * from './Abstract/Component/ComponentViewerEvent'
export * from './Abstract/Component/ComponentMessage'
export * from './Abstract/Component/ComponentEventObserver'
export * from './Abstract/Component/IncomingComponentItemPayload'
export * from './Abstract/Component/KeyboardModifier'
export * from './Abstract/Component/MessageData'
export * from './Abstract/Component/PermissionDialog'
export * from './Abstract/Content/ItemContent'
export * from './Abstract/Contextual'
export * from './Abstract/Contextual/BackupFile'
export * from './Abstract/Contextual/BackupFileDecryptedContextualPayload'
export * from './Abstract/Contextual/BackupFileEncryptedContextualPayload'
export * from './Abstract/Contextual/ComponentCreate'
export * from './Abstract/Contextual/ComponentRetrieved'
export * from './Abstract/Contextual/ContextPayload'
export * from './Abstract/Contextual/FilteredServerItem'
export * from './Abstract/Contextual/Functions'
export * from './Abstract/Contextual/LocalStorage'
export * from './Abstract/Contextual/OfflineSyncPush'
export * from './Abstract/Contextual/OfflineSyncSaved'
export * from './Abstract/Contextual/ServerSyncPush'
export * from './Abstract/Contextual/ServerSyncSaved'
export * from './Abstract/Contextual/SessionHistory'
export * from './Abstract/Item'
export * from './Abstract/Payload'
export * from './Abstract/TransferPayload'

View file

@ -24,8 +24,8 @@
},
"dependencies": {
"@standardnotes/auth": "^3.19.4",
"@standardnotes/common": "^1.23.1",
"@standardnotes/models": "workspace:*",
"@standardnotes/common": "^1.30.0",
"@standardnotes/models": "workspace:^",
"@standardnotes/responses": "workspace:*",
"@standardnotes/security": "^1.2.0",
"@standardnotes/utils": "workspace:*",

View file

@ -0,0 +1,7 @@
import { ApplicationInterface } from './ApplicationInterface'
import { DeinitCallback } from './DeinitCallback'
export interface AppGroupManagedApplication extends ApplicationInterface {
onDeinit: DeinitCallback
setOnDeinit(onDeinit: DeinitCallback): void
}

View file

@ -1,22 +1,44 @@
import { ApplicationIdentifier } from '@standardnotes/common'
import { ApplicationIdentifier, ContentType } from '@standardnotes/common'
import { BackupFile, DecryptedItemInterface, ItemStream, PrefKey, PrefValue } from '@standardnotes/models'
import { ComponentManagerInterface } from '../Component/ComponentManagerInterface'
import { ApplicationEvent } from '../Event/ApplicationEvent'
import { ApplicationEventCallback } from '../Event/ApplicationEventCallback'
import { FeaturesClientInterface } from '../Feature/FeaturesClientInterface'
import { ItemsClientInterface } from '../Item/ItemsClientInterface'
import { MutatorClientInterface } from '../Mutator/MutatorClientInterface'
import { StorageValueModes } from '../Storage/StorageTypes'
import { DeinitCallback } from './DeinitCallback'
import { DeinitMode } from './DeinitMode'
import { DeinitSource } from './DeinitSource'
import { UserClientInterface } from './UserClientInterface'
export interface ApplicationInterface {
deinit(mode: DeinitMode, source: DeinitSource): void
getDeinitMode(): DeinitMode
isStarted(): boolean
isLaunched(): boolean
addEventObserver(callback: ApplicationEventCallback, singleEvent?: ApplicationEvent): () => void
hasProtectionSources(): boolean
createEncryptedBackupFileForAutomatedDesktopBackups(): Promise<BackupFile | undefined>
createDecryptedBackupFile(): Promise<BackupFile | undefined>
hasPasscode(): boolean
lock(): Promise<void>
setValue(key: string, value: unknown, mode?: StorageValueModes): void
getValue(key: string, mode?: StorageValueModes): unknown
removeValue(key: string, mode?: StorageValueModes): Promise<void>
isLocked(): Promise<boolean>
getPreference<K extends PrefKey>(key: K): PrefValue[K] | undefined
getPreference<K extends PrefKey>(key: K, defaultValue: PrefValue[K]): PrefValue[K]
getPreference<K extends PrefKey>(key: K, defaultValue?: PrefValue[K]): PrefValue[K] | undefined
streamItems<I extends DecryptedItemInterface = DecryptedItemInterface>(
contentType: ContentType | ContentType[],
stream: ItemStream<I>,
): () => void
get features(): FeaturesClientInterface
get componentManager(): ComponentManagerInterface
get items(): ItemsClientInterface
get mutator(): MutatorClientInterface
get user(): UserClientInterface
readonly identifier: ApplicationIdentifier
}
export interface AppGroupManagedApplication extends ApplicationInterface {
onDeinit: DeinitCallback
setOnDeinit(onDeinit: DeinitCallback): void
}

View file

@ -1,5 +1,5 @@
import { DeinitSource } from './DeinitSource'
import { DeinitMode } from './DeinitMode'
import { AppGroupManagedApplication } from './ApplicationInterface'
import { AppGroupManagedApplication } from './AppGroupManagedApplication'
export type DeinitCallback = (application: AppGroupManagedApplication, mode: DeinitMode, source: DeinitSource) => void

View file

@ -0,0 +1,8 @@
import { DesktopManagerInterface } from '../Device/DesktopManagerInterface'
import { WebAppEvent } from '../Event/WebAppEvent'
import { ApplicationInterface } from './ApplicationInterface'
export interface WebApplicationInterface extends ApplicationInterface {
notifyWebEvent(event: WebAppEvent, data?: unknown): void
getDesktopService(): DesktopManagerInterface | undefined
}

View file

@ -0,0 +1,23 @@
import { Uuid } from '@standardnotes/common'
import { ComponentArea } from '@standardnotes/features'
import { ActionObserver, PermissionDialog, SNComponent, SNNote } from '@standardnotes/models'
import { DesktopManagerInterface } from '../Device/DesktopManagerInterface'
import { ComponentViewerInterface } from './ComponentViewerInterface'
export interface ComponentManagerInterface {
urlForComponent(component: SNComponent): string | undefined
setDesktopManager(desktopManager: DesktopManagerInterface): void
componentsForArea(area: ComponentArea): SNComponent[]
editorForNote(note: SNNote): SNComponent | undefined
doesEditorChangeRequireAlert(from: SNComponent | undefined, to: SNComponent | undefined): boolean
showEditorChangeAlert(): Promise<boolean>
destroyComponentViewer(viewer: ComponentViewerInterface): void
createComponentViewer(
component: SNComponent,
contextItem?: Uuid,
actionObserver?: ActionObserver,
urlOverride?: string,
): ComponentViewerInterface
presentPermissionsDialog(_dialog: PermissionDialog): void
}

View file

@ -0,0 +1,4 @@
export enum ComponentViewerError {
OfflineRestricted = 'OfflineRestricted',
MissingUrl = 'MissingUrl',
}

View file

@ -0,0 +1,29 @@
import {
ActionObserver,
ComponentEventObserver,
ComponentMessage,
DecryptedItemInterface,
SNComponent,
} from '@standardnotes/models'
import { FeatureStatus } from '../Feature/FeatureStatus'
import { ComponentViewerError } from './ComponentViewerError'
export interface ComponentViewerInterface {
readonly component: SNComponent
readonly url?: string
identifier: string
lockReadonly: boolean
sessionKey?: string
overrideContextItem?: DecryptedItemInterface
get componentUuid(): string
destroy(): void
setReadonly(readonly: boolean): void
getFeatureStatus(): FeatureStatus
shouldRender(): boolean
getError(): ComponentViewerError | undefined
setWindow(window: Window): void
addEventObserver(observer: ComponentEventObserver): () => void
addActionObserver(observer: ActionObserver): () => void
postActiveThemes(): void
handleMessage(message: ComponentMessage): void
}

View file

@ -0,0 +1,7 @@
import { SNComponent } from '@standardnotes/models'
export interface DesktopManagerInterface {
syncComponentsInstallation(components: SNComponent[]): void
registerUpdateObserver(callback: (component: SNComponent) => void): () => void
getExtServerHost(): string
}

View file

@ -0,0 +1,65 @@
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,
}

View file

@ -0,0 +1,3 @@
import { ApplicationEvent } from './ApplicationEvent'
export type ApplicationEventCallback = (event: ApplicationEvent, data?: unknown) => Promise<void>

View file

@ -0,0 +1,6 @@
export enum FeatureStatus {
NoUserSubscription = 'NoUserSubscription',
NotInCurrentPlan = 'NotInCurrentPlan',
InCurrentPlanButExpired = 'InCurrentPlanButExpired',
Entitled = 'Entitled',
}

View file

@ -1,8 +1,10 @@
import { FeatureStatus, SetOfflineFeaturesFunctionResponse } from './Types'
import { FeatureDescription, FeatureIdentifier } from '@standardnotes/features'
import { SNComponent } from '@standardnotes/models'
import { RoleName } from '@standardnotes/common'
import { FeatureStatus } from './FeatureStatus'
import { SetOfflineFeaturesFunctionResponse } from './SetOfflineFeaturesFunctionResponse'
export interface FeaturesClientInterface {
downloadExternalFeature(urlOrCode: string): Promise<SNComponent | undefined>

View file

@ -0,0 +1,4 @@
export enum FeaturesEvent {
UserRolesChanged = 'UserRolesChanged',
FeaturesUpdated = 'FeaturesUpdated',
}

View file

@ -0,0 +1,4 @@
export type OfflineSubscriptionEntitlements = {
featuresUrl: string
extensionKey: string
}

View file

@ -0,0 +1,3 @@
import { ClientDisplayableError } from '@standardnotes/responses'
export type SetOfflineFeaturesFunctionResponse = ClientDisplayableError | undefined

View file

@ -1,5 +1,4 @@
import { SNItemsKey } from '@standardnotes/encryption'
import { ContentType } from '@standardnotes/common'
import { ContentType, Uuid } from '@standardnotes/common'
import {
SNNote,
FileItem,
@ -14,8 +13,8 @@ import {
SNComponent,
SNTheme,
DisplayOptions,
ItemsKeyInterface,
} from '@standardnotes/models'
import { UuidString } from '@Lib/Types'
export interface ItemsClientInterface {
get invalidItems(): EncryptedItemInterface[]
@ -43,7 +42,7 @@ export interface ItemsClientInterface {
getDisplayableTags(): SNTag[]
getDisplayableItemsKeys(): SNItemsKey[]
getDisplayableItemsKeys(): ItemsKeyInterface[]
getDisplayableFiles(): FileItem[]
@ -116,14 +115,14 @@ export interface ItemsClientInterface {
/**
* Finds an item by UUID.
*/
findItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: UuidString): T | undefined
findItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: Uuid): T | undefined
/**
* Finds an item by predicate.
*/
findItems<T extends DecryptedItemInterface>(uuids: UuidString[]): T[]
findItems<T extends DecryptedItemInterface>(uuids: Uuid[]): T[]
findSureItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: UuidString): T
findSureItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: Uuid): T
/**
* Finds an item by predicate.

View file

@ -0,0 +1,184 @@
import { ContentType } from '@standardnotes/common'
import {
BackupFile,
DecryptedItemInterface,
DecryptedItemMutator,
EncryptedItemInterface,
FileItem,
ItemContent,
PayloadEmitSource,
SmartView,
SNComponent,
SNNote,
SNTag,
TransactionalMutation,
} from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { ChallengeReason } from '../Challenge/Types/ChallengeReason'
import { SyncOptions } from '../Sync/SyncOptions'
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: DecryptedItemInterface): Promise<DecryptedItemInterface>
/**
* Mutates a pre-existing item, marks it as dirty, and syncs it
*/
changeAndSaveItem<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemToLookupUuidFor: DecryptedItemInterface,
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
emitSource?: PayloadEmitSource,
syncOptions?: SyncOptions,
): Promise<DecryptedItemInterface | undefined>
/**
* Mutates pre-existing items, marks them as dirty, and syncs
*/
changeAndSaveItems<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemsToLookupUuidsFor: DecryptedItemInterface[],
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
emitSource?: PayloadEmitSource,
syncOptions?: SyncOptions,
): Promise<void>
/**
* Mutates a pre-existing item and marks it as dirty. Does not sync changes.
*/
changeItem<M extends DecryptedItemMutator>(
itemToLookupUuidFor: DecryptedItemInterface,
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
): Promise<DecryptedItemInterface | undefined>
/**
* Mutates a pre-existing items and marks them as dirty. Does not sync changes.
*/
changeItems<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemsToLookupUuidsFor: DecryptedItemInterface[],
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
): Promise<(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?: PayloadEmitSource,
payloadSourceKey?: string,
): Promise<(DecryptedItemInterface | undefined)[]>
runTransactionalMutation(
transaction: TransactionalMutation,
emitSource?: PayloadEmitSource,
payloadSourceKey?: string,
): Promise<DecryptedItemInterface | undefined>
protectItems<_M extends DecryptedItemMutator<ItemContent>, I extends DecryptedItemInterface<ItemContent>>(
items: I[],
): Promise<I[]>
unprotectItems<_M extends DecryptedItemMutator<ItemContent>, I extends DecryptedItemInterface<ItemContent>>(
items: I[],
reason: ChallengeReason,
): Promise<I[] | undefined>
protectNote(note: SNNote): Promise<SNNote>
unprotectNote(note: SNNote): Promise<SNNote | undefined>
protectNotes(notes: SNNote[]): Promise<SNNote[]>
unprotectNotes(notes: SNNote[]): Promise<SNNote[]>
protectFile(file: FileItem): Promise<FileItem>
unprotectFile(file: FileItem): Promise<FileItem | undefined>
/**
* Takes the values of the input item and emits it onto global state.
*/
mergeItem(item: DecryptedItemInterface, source: PayloadEmitSource): Promise<DecryptedItemInterface>
/**
* Creates an unmanaged item that can be added later.
*/
createTemplateItem<
C extends ItemContent = ItemContent,
I extends DecryptedItemInterface<C> = DecryptedItemInterface<C>,
>(
contentType: ContentType,
content?: C,
): I
/**
* @param isUserModified Whether to change the modified date the user
* sees of the item.
*/
setItemNeedsSync(item: DecryptedItemInterface, isUserModified?: boolean): Promise<DecryptedItemInterface | undefined>
setItemsNeedsSync(items: DecryptedItemInterface[]): Promise<(DecryptedItemInterface | undefined)[]>
deleteItem(item: DecryptedItemInterface | EncryptedItemInterface): Promise<void>
deleteItems(items: (DecryptedItemInterface | EncryptedItemInterface)[]): Promise<void>
emptyTrash(): Promise<void>
duplicateItem<T extends 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: SNTag, childTag: SNTag): Promise<void>
/**
* Remove the tag parent.
*/
unsetTagParent(childTag: SNTag): Promise<void>
findOrCreateTag(title: string): Promise<SNTag>
/** Creates and returns the tag but does not run sync. Callers must perform sync. */
createTagOrSmartView(title: string): Promise<SNTag | SmartView>
/**
* Activates or deactivates a component, depending on its
* current state, and syncs.
*/
toggleComponent(component: SNComponent): Promise<void>
toggleTheme(theme: 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: DecryptedItemInterface[]
errorCount: number
}
| {
error: ClientDisplayableError
}
>
}

View file

@ -1,13 +1,19 @@
export * from './Alert/AlertService'
export * from './Api/ApiServiceInterface'
export * from './Application/AppGroupManagedApplication'
export * from './Application/ApplicationInterface'
export * from './Application/ApplicationStage'
export * from './Application/DeinitCallback'
export * from './Application/DeinitSource'
export * from './Application/DeinitMode'
export * from './Application/UserClientInterface'
export * from './Application/ApplicationInterface'
export * from './Application/WebApplicationInterface'
export * from './Challenge'
export * from './Component/ComponentManagerInterface'
export * from './Component/ComponentViewerError'
export * from './Component/ComponentViewerInterface'
export * from './Device/DesktopDeviceInterface'
export * from './Device/DesktopManagerInterface'
export * from './Device/DesktopWebCommunication'
export * from './Device/DeviceInterface'
export * from './Device/Environments'
@ -16,9 +22,17 @@ export * from './Device/MobileDeviceInterface'
export * from './Device/TypeCheck'
export * from './Device/WebOrDesktopDeviceInterface'
export * from './Diagnostics/ServiceDiagnostics'
export * from './Event/ApplicationEvent'
export * from './Event/ApplicationEventCallback'
export * from './Event/EventObserver'
export * from './Event/SyncEvent'
export * from './Event/SyncEventReceiver'
export * from './Event/WebAppEvent'
export * from './Feature/FeatureStatus'
export * from './Feature/FeaturesClientInterface'
export * from './Feature/FeaturesEvent'
export * from './Feature/OfflineSubscriptionEntitlements'
export * from './Feature/SetOfflineFeaturesFunctionResponse'
export * from './Files/FilesApiInterface'
export * from './FileSystem/FileSystemApi'
export * from './Integrity/IntegrityApiInterface'
@ -32,7 +46,9 @@ export * from './Internal/InternalEventInterface'
export * from './Internal/InternalEventPublishStrategy'
export * from './Internal/InternalEventType'
export * from './Item/ItemManagerInterface'
export * from './Item/ItemsClientInterface'
export * from './Item/ItemsServerInterface'
export * from './Mutator/MutatorClientInterface'
export * from './Payloads/PayloadManagerInterface'
export * from './Preferences/PreferenceServiceInterface'
export * from './Service/AbstractService'

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

@ -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,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'

View file

@ -0,0 +1,3 @@
node_modules
dist
coverage

View file

@ -0,0 +1,9 @@
{
"extends": "../../.eslintrc",
"parserOptions": {
"project": "./linter.tsconfig.json"
},
"rules": {
"@typescript-eslint/no-explicit-any": ["warn", { "ignoreRestArgs": true }]
}
}

1
packages/ui-services/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
dist

View file

@ -0,0 +1,11 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const base = require('../../node_modules/@standardnotes/config/src/jest.json');
module.exports = {
...base,
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
},
}
};

View file

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["dist"]
}

View file

@ -0,0 +1,41 @@
{
"name": "@standardnotes/ui-services",
"version": "1.0.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
"description": "UI Services for Standard Notes clients",
"main": "dist/index.js",
"author": "Standard Notes",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"private": true,
"license": "AGPL-3.0-or-later",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
"start": "tsc -p tsconfig.json --watch",
"prebuild": "yarn clean",
"build": "tsc -p tsconfig.json",
"lint": "eslint . --ext .ts",
"test:unit": "jest spec --coverage"
},
"dependencies": {
"@standardnotes/filepicker": "workspace:^",
"@standardnotes/services": "workspace:^",
"@standardnotes/snjs": "workspace:^",
"@standardnotes/styles": "workspace:^",
"@standardnotes/toast": "workspace:^",
"@standardnotes/utils": "workspace:^"
},
"devDependencies": {
"@types/jest": "^28.1.5",
"@typescript-eslint/eslint-plugin": "^5.30.0",
"@typescript-eslint/parser": "^5.12.1",
"eslint-plugin-prettier": "*",
"jest": "^28.1.2",
"ts-jest": "^28.0.5"
}
}

View file

@ -1,4 +1,3 @@
import { AlertService, ButtonType } from '@standardnotes/services'
import { sanitizeHtmlString } from '@standardnotes/utils'
import { SKAlert } from '@standardnotes/styles'
@ -65,37 +64,3 @@ export function alertDialog({
alert.present()
})
}
export class WebAlertService extends AlertService {
alert(text: string, title?: string, closeButtonText?: string) {
return alertDialog({ text, title, closeButtonText })
}
confirm(
text: string,
title?: string,
confirmButtonText?: string,
confirmButtonType?: ButtonType,
cancelButtonText?: string,
): Promise<boolean> {
return confirmDialog({
text,
title,
confirmButtonText,
cancelButtonText,
confirmButtonStyle: confirmButtonType === ButtonType.Danger ? 'danger' : 'info',
})
}
blockingDialog(text: string, title?: string) {
const alert = new SKAlert({
title: title && sanitizeHtmlString(title),
text: sanitizeHtmlString(text),
buttons: [],
})
alert.present()
return () => {
alert.dismiss()
}
}
}

View file

@ -0,0 +1,38 @@
import { AlertService, ButtonType } from '@standardnotes/services'
import { sanitizeHtmlString } from '@standardnotes/utils'
import { SKAlert } from '@standardnotes/styles'
import { alertDialog, confirmDialog } from './Functions'
export class WebAlertService extends AlertService {
alert(text: string, title?: string, closeButtonText?: string) {
return alertDialog({ text, title, closeButtonText })
}
confirm(
text: string,
title?: string,
confirmButtonText?: string,
confirmButtonType?: ButtonType,
cancelButtonText?: string,
): Promise<boolean> {
return confirmDialog({
text,
title,
confirmButtonText,
cancelButtonText,
confirmButtonStyle: confirmButtonType === ButtonType.Danger ? 'danger' : 'info',
})
}
blockingDialog(text: string, title?: string) {
const alert = new SKAlert({
title: title && sanitizeHtmlString(title),
text: sanitizeHtmlString(text),
buttons: [],
})
alert.present()
return () => {
alert.dismiss()
}
}
}

View file

@ -1,4 +1,3 @@
import { WebApplication } from '@/Application/Application'
import { parseFileName } from '@standardnotes/filepicker'
import {
BackupFile,
@ -6,6 +5,7 @@ import {
BackupFileDecryptedContextualPayload,
NoteContent,
EncryptedItemInterface,
SNApplication,
} from '@standardnotes/snjs'
function sanitizeFileName(name: string): string {
@ -27,10 +27,10 @@ type ZippableData = {
type ObjectURL = string
export class ArchiveManager {
private readonly application: WebApplication
private readonly application: SNApplication
private textFile?: string
constructor(application: WebApplication) {
constructor(application: SNApplication) {
this.application = application
}

View file

@ -1,5 +1,3 @@
import { useCallback, useState } from 'react'
export enum StorageKey {
AnonymousUserId = 'AnonymousUserId',
ShowBetaWarning = 'ShowBetaWarning',
@ -26,19 +24,3 @@ export const storage = {
localStorage.removeItem(key)
},
}
type LocalStorageHookReturnType<Key extends StorageKey> = [StorageValue[Key] | null, (value: StorageValue[Key]) => void]
export const useLocalStorageItem = <Key extends StorageKey>(key: Key): LocalStorageHookReturnType<Key> => {
const [value, setValue] = useState(() => storage.get(key))
const set = useCallback(
(value: StorageValue[Key]) => {
storage.set(key, value)
setValue(value)
},
[key],
)
return [value, set]
}

View file

@ -1,4 +1,3 @@
import { WebApplication } from '@/Application/Application'
import {
StorageValueModes,
ApplicationService,
@ -13,6 +12,7 @@ import {
InternalEventBus,
PayloadEmitSource,
LocalStorageDecryptedContextualPayload,
WebApplicationInterface,
} from '@standardnotes/snjs'
import { dismissToast, ToastType, addTimedToast } from '@standardnotes/toast'
@ -26,7 +26,7 @@ export class ThemeManager extends ApplicationService {
private unregisterStream!: () => void
private lastUseDeviceThemeSettings = false
constructor(application: WebApplication) {
constructor(application: WebApplicationInterface) {
super(application, new InternalEventBus())
this.colorSchemeEventHandler = this.colorSchemeEventHandler.bind(this)
}
@ -80,7 +80,7 @@ export class ThemeManager extends ApplicationService {
}
get webApplication() {
return this.application as WebApplication
return this.application as WebApplicationInterface
}
override deinit() {

View file

@ -0,0 +1,7 @@
export * from './Alert/Functions'
export * from './Alert/WebAlertService'
export * from './Archive/ArchiveManager'
export * from './IO/IOService'
export * from './Security/AutolockService'
export * from './Storage/LocalStorage'
export * from './Theme/ThemeManager'

View file

@ -0,0 +1,14 @@
{
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
"compilerOptions": {
"skipLibCheck": true,
"rootDir": "./src",
"outDir": "./dist",
"jsx": "react-jsx",
},
"include": [
"src/**/*"
],
"references": [],
"exclude": ["**/*.spec.ts", "dist", "node_modules"]
}

View file

@ -82,6 +82,7 @@
"@standardnotes/snjs": "workspace:*",
"@standardnotes/styles": "workspace:*",
"@standardnotes/toast": "workspace:*",
"@standardnotes/ui-services": "workspace:^",
"@zip.js/zip.js": "^2.4.10",
"mobx": "^6.5.0",
"mobx-react-lite": "^3.3.0",

View file

@ -1,11 +1,5 @@
import { WebCrypto } from '@/Application/Crypto'
import { WebAlertService } from '@/Services/AlertService'
import { ArchiveManager } from '@/Services/ArchiveManager'
import { AutolockService } from '@/Services/AutolockService'
import { DesktopManager } from '@/Services/DesktopManager'
import { IOService } from '@/Services/IOService'
import { ThemeManager } from '@/Services/ThemeManager'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { WebOrDesktopDevice } from '@/Application/Device/WebOrDesktopDevice'
import {
DeinitSource,
@ -21,11 +15,14 @@ import {
SNTag,
ContentType,
DecryptedItemInterface,
WebAppEvent,
WebApplicationInterface,
} from '@standardnotes/snjs'
import { makeObservable, observable } from 'mobx'
import { PanelResizedData } from '@/Types/PanelResizedData'
import { WebAppEvent } from './WebAppEvent'
import { isDesktopApplication } from '@/Utils'
import { DesktopManager } from './Device/DesktopManager'
import { ArchiveManager, AutolockService, IOService, ThemeManager, WebAlertService } from '@standardnotes/ui-services'
type WebServices = {
viewControllerManager: ViewControllerManager
@ -38,7 +35,7 @@ type WebServices = {
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
export class WebApplication extends SNApplication {
export class WebApplication extends SNApplication implements WebApplicationInterface {
private webServices!: WebServices
private webEventObservers: WebEventObserver[] = []
public itemControllerGroup: ItemGroupController

View file

@ -6,14 +6,12 @@ import {
InternalEventBus,
isDesktopDevice,
} from '@standardnotes/snjs'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ArchiveManager, IOService, AutolockService, ThemeManager } from '@standardnotes/ui-services'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { getPlatform, isDesktopApplication } from '@/Utils'
import { ArchiveManager } from '@/Services/ArchiveManager'
import { DesktopManager } from '@/Services/DesktopManager'
import { IOService } from '@/Services/IOService'
import { AutolockService } from '@/Services/AutolockService'
import { ThemeManager } from '@/Services/ThemeManager'
import { WebOrDesktopDevice } from '@/Application/Device/WebOrDesktopDevice'
import { DesktopManager } from './Device/DesktopManager'
const createApplication = (
descriptor: ApplicationDescriptor,

View file

@ -12,9 +12,9 @@ import {
assert,
DesktopClientRequiresWebMethods,
DesktopDeviceInterface,
WebApplicationInterface,
WebAppEvent,
} from '@standardnotes/snjs'
import { WebApplication } from '@/Application/Application'
import { WebAppEvent } from '@/Application/WebAppEvent'
export class DesktopManager
extends ApplicationService
@ -27,12 +27,12 @@ export class DesktopManager
dataLoaded = false
lastSearchedText?: string
constructor(application: WebApplication, private device: DesktopDeviceInterface) {
constructor(application: WebApplicationInterface, private device: DesktopDeviceInterface) {
super(application, new InternalEventBus())
}
get webApplication() {
return this.application as WebApplication
return this.application as WebApplicationInterface
}
override deinit() {
@ -80,7 +80,7 @@ export class DesktopManager
.catch(console.error)
}
registerUpdateObserver(callback: (component: SNComponent) => void) {
registerUpdateObserver(callback: (component: SNComponent) => void): () => void {
const observer = {
callback: callback,
}

View file

@ -1,6 +1,6 @@
import { ApplicationEvent } from '@standardnotes/snjs'
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { autorun, IReactionDisposer, IReactionPublic } from 'mobx'
import { Component } from 'react'

View file

@ -1,5 +1,5 @@
import { observer } from 'mobx-react-lite'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { WebApplication } from '@/Application/Application'
import { useCallback, FunctionComponent, KeyboardEventHandler } from 'react'
import { ApplicationGroup } from '@/Application/ApplicationGroup'

View file

@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import { ChangeEventHandler, FunctionComponent, useCallback, useEffect, useState } from 'react'
import Checkbox from '@/Components/Checkbox/Checkbox'

View file

@ -1,6 +1,6 @@
import { STRING_NON_MATCHING_PASSWORDS } from '@/Constants/Strings'
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, KeyboardEventHandler, useCallback, useEffect, useRef, useState } from 'react'
import { AccountMenuPane } from './AccountMenuPane'

View file

@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, KeyboardEventHandler, useCallback, useEffect, useRef, useState } from 'react'
import { AccountMenuPane } from './AccountMenuPane'

View file

@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import Icon from '@/Components/Icon/Icon'
import { SyncQueueStrategy } from '@standardnotes/snjs'

View file

@ -1,6 +1,6 @@
import { WebApplication } from '@/Application/Application'
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useState } from 'react'
import { AccountMenuPane } from './AccountMenuPane'

View file

@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { isDev } from '@/Utils'
import { observer } from 'mobx-react-lite'
import React, { FunctionComponent, KeyboardEventHandler, useCallback, useEffect, useRef, useState } from 'react'

View file

@ -1,5 +1,5 @@
import { observer } from 'mobx-react-lite'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { WebApplication } from '@/Application/Application'
import { User as UserType } from '@standardnotes/snjs'

View file

@ -1,7 +1,7 @@
import Icon from '@/Components/Icon/Icon'
import MenuItem from '@/Components/Menu/MenuItem'
import { MenuItemType } from '@/Components/Menu/MenuItemType'
import { KeyboardKey } from '@/Services/IOService'
import { KeyboardKey } from '@standardnotes/ui-services'
import { ApplicationDescriptor } from '@standardnotes/snjs'
import {
ChangeEventHandler,

View file

@ -1,5 +1,5 @@
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { ApplicationDescriptor, ApplicationGroupEvent, ButtonType } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useState } from 'react'

View file

@ -1,6 +1,6 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon'

View file

@ -1,10 +1,9 @@
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { getPlatformString, getWindowUrlParams } from '@/Utils'
import { ApplicationEvent, Challenge, removeFromArray } from '@standardnotes/snjs'
import { ApplicationEvent, Challenge, removeFromArray, WebAppEvent } from '@standardnotes/snjs'
import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
import { alertDialog } from '@/Services/AlertService'
import { alertDialog } from '@standardnotes/ui-services'
import { WebApplication } from '@/Application/Application'
import { WebAppEvent } from '@/Application/WebAppEvent'
import Navigation from '@/Components/Navigation/Navigation'
import NoteGroupView from '@/Components/NoteGroupView/NoteGroupView'
import Footer from '@/Components/Footer/Footer'

View file

@ -1,5 +1,5 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { KeyboardKey } from '@/Services/IOService'
import { KeyboardKey } from '@standardnotes/ui-services'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { FileItem } from '@standardnotes/snjs'
import {

View file

@ -15,7 +15,7 @@ import Icon from '@/Components/Icon/Icon'
import ChallengeModalPrompt from './ChallengePrompt'
import LockscreenWorkspaceSwitcher from './LockscreenWorkspaceSwitcher'
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { ChallengeModalValues } from './ChallengeModalValues'
type Props = {

View file

@ -1,5 +1,5 @@
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { FunctionComponent, useCallback, useRef, useState } from 'react'
import WorkspaceSwitcherMenu from '@/Components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu'
import Button from '@/Components/Button/Button'

View file

@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon'

View file

@ -3,7 +3,7 @@ import {
FeatureStatus,
SNComponent,
dateToLocalizedString,
ComponentViewer,
ComponentViewerInterface,
ComponentViewerEvent,
ComponentViewerError,
} from '@standardnotes/snjs'
@ -19,8 +19,8 @@ import { openSubscriptionDashboard } from '@/Utils/ManageSubscription'
interface IProps {
application: WebApplication
componentViewer: ComponentViewer
requestReload?: (viewer: ComponentViewer, force?: boolean) => void
componentViewer: ComponentViewerInterface
requestReload?: (viewer: ComponentViewerInterface, force?: boolean) => void
onLoad?: (component: SNComponent) => void
}

Some files were not shown because too many files have changed in this diff Show more