feat(web): extract ui-services package

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

View File

@@ -28,6 +28,7 @@
"build:desktop": "yarn workspaces foreach -pt --verbose -R --from @standardnotes/desktop --exclude @standardnotes/components-meta run build", "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: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: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:web": "lerna run start --scope=@standardnotes/web",
"start:server:e2e": "lerna run start:test-server --scope=@standardnotes/snjs", "start:server:e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
"prepare": "husky install", "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, ProtocolVersion,
} from '@standardnotes/common' } from '@standardnotes/common'
import { import {
BackupFile,
CreateDecryptedItemFromPayload, CreateDecryptedItemFromPayload,
CreatePayloadSplit, CreatePayloadSplit,
DecryptedPayload, DecryptedPayload,
@@ -28,7 +29,6 @@ import { CreateAnyKeyParams } from '../Keys/RootKey/KeyParamsFunctions'
import { SNRootKey } from '../Keys/RootKey/RootKey' import { SNRootKey } from '../Keys/RootKey/RootKey'
import { SNRootKeyParams } from '../Keys/RootKey/RootKeyParams' import { SNRootKeyParams } from '../Keys/RootKey/RootKeyParams'
import { EncryptionService } from '../Service/Encryption/EncryptionService' import { EncryptionService } from '../Service/Encryption/EncryptionService'
import { BackupFile } from './BackupFile'
import { BackupFileType } from './BackupFileType' import { BackupFileType } from './BackupFileType'
export async function DecryptBackupFile( export async function DecryptBackupFile(

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
export * from './Algorithm' export * from './Algorithm'
export * from './Backups/BackupFile'
export * from './Backups/BackupFileDecryptor' export * from './Backups/BackupFileDecryptor'
export * from './Backups/BackupFileType' export * from './Backups/BackupFileType'
export * from './Keys/ItemsKey/ItemsKey' 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 { AnyKeyParamsContent, ProtocolVersion } from '@standardnotes/common'
import { ContextPayload } from './ContextPayload' import { BackupFileDecryptedContextualPayload } from './BackupFileDecryptedContextualPayload'
import { ItemContent } from '../Content/ItemContent' import { BackupFileEncryptedContextualPayload } from './BackupFileEncryptedContextualPayload'
import { DecryptedTransferPayload, EncryptedTransferPayload } from '../TransferPayload'
export interface BackupFileEncryptedContextualPayload extends ContextPayload { export type BackupFile = {
auth_hash?: string version?: ProtocolVersion
content: string keyParams?: AnyKeyParamsContent
created_at_timestamp: number auth_params?: AnyKeyParamsContent
created_at: Date items: (BackupFileDecryptedContextualPayload | BackupFileEncryptedContextualPayload)[]
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,
}
} }

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/DecryptedItemMutator'
export * from './Mutator/DeleteMutator' export * from './Mutator/DeleteMutator'
export * from './Mutator/ItemMutator' export * from './Mutator/ItemMutator'
export * from './Types/AppDataField' export * from './Mutator/TransactionalMutation'
export * from './Types/AppDataField' export * from './Types/AppDataField'
export * from './Types/ConflictStrategy' export * from './Types/ConflictStrategy'
export * from './Types/DefaultAppDomain' export * from './Types/DefaultAppDomain'
export * from './Types/DefaultAppDomain' export * from './Types/ItemStream'
export * from './Types/MutationType' export * from './Types/MutationType'
export * from './Types/SingletonStrategy' 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/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/Item'
export * from './Abstract/Payload' export * from './Abstract/Payload'
export * from './Abstract/TransferPayload' export * from './Abstract/TransferPayload'

View File

@@ -24,8 +24,8 @@
}, },
"dependencies": { "dependencies": {
"@standardnotes/auth": "^3.19.4", "@standardnotes/auth": "^3.19.4",
"@standardnotes/common": "^1.23.1", "@standardnotes/common": "^1.30.0",
"@standardnotes/models": "workspace:*", "@standardnotes/models": "workspace:^",
"@standardnotes/responses": "workspace:*", "@standardnotes/responses": "workspace:*",
"@standardnotes/security": "^1.2.0", "@standardnotes/security": "^1.2.0",
"@standardnotes/utils": "workspace:*", "@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 { DeinitMode } from './DeinitMode'
import { DeinitSource } from './DeinitSource' import { DeinitSource } from './DeinitSource'
import { UserClientInterface } from './UserClientInterface' import { UserClientInterface } from './UserClientInterface'
export interface ApplicationInterface { export interface ApplicationInterface {
deinit(mode: DeinitMode, source: DeinitSource): void deinit(mode: DeinitMode, source: DeinitSource): void
getDeinitMode(): DeinitMode 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 get user(): UserClientInterface
readonly identifier: ApplicationIdentifier 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 { DeinitSource } from './DeinitSource'
import { DeinitMode } from './DeinitMode' import { DeinitMode } from './DeinitMode'
import { AppGroupManagedApplication } from './ApplicationInterface' import { AppGroupManagedApplication } from './AppGroupManagedApplication'
export type DeinitCallback = (application: AppGroupManagedApplication, mode: DeinitMode, source: DeinitSource) => void 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 { FeatureDescription, FeatureIdentifier } from '@standardnotes/features'
import { SNComponent } from '@standardnotes/models' import { SNComponent } from '@standardnotes/models'
import { RoleName } from '@standardnotes/common' import { RoleName } from '@standardnotes/common'
import { FeatureStatus } from './FeatureStatus'
import { SetOfflineFeaturesFunctionResponse } from './SetOfflineFeaturesFunctionResponse'
export interface FeaturesClientInterface { export interface FeaturesClientInterface {
downloadExternalFeature(urlOrCode: string): Promise<SNComponent | undefined> 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, Uuid } from '@standardnotes/common'
import { ContentType } from '@standardnotes/common'
import { import {
SNNote, SNNote,
FileItem, FileItem,
@@ -14,8 +13,8 @@ import {
SNComponent, SNComponent,
SNTheme, SNTheme,
DisplayOptions, DisplayOptions,
ItemsKeyInterface,
} from '@standardnotes/models' } from '@standardnotes/models'
import { UuidString } from '@Lib/Types'
export interface ItemsClientInterface { export interface ItemsClientInterface {
get invalidItems(): EncryptedItemInterface[] get invalidItems(): EncryptedItemInterface[]
@@ -43,7 +42,7 @@ export interface ItemsClientInterface {
getDisplayableTags(): SNTag[] getDisplayableTags(): SNTag[]
getDisplayableItemsKeys(): SNItemsKey[] getDisplayableItemsKeys(): ItemsKeyInterface[]
getDisplayableFiles(): FileItem[] getDisplayableFiles(): FileItem[]
@@ -116,14 +115,14 @@ export interface ItemsClientInterface {
/** /**
* Finds an item by UUID. * 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. * 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. * 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 './Alert/AlertService'
export * from './Api/ApiServiceInterface' export * from './Api/ApiServiceInterface'
export * from './Application/AppGroupManagedApplication'
export * from './Application/ApplicationInterface'
export * from './Application/ApplicationStage' export * from './Application/ApplicationStage'
export * from './Application/DeinitCallback' export * from './Application/DeinitCallback'
export * from './Application/DeinitSource' export * from './Application/DeinitSource'
export * from './Application/DeinitMode' export * from './Application/DeinitMode'
export * from './Application/UserClientInterface' export * from './Application/UserClientInterface'
export * from './Application/ApplicationInterface' export * from './Application/WebApplicationInterface'
export * from './Challenge' export * from './Challenge'
export * from './Component/ComponentManagerInterface'
export * from './Component/ComponentViewerError'
export * from './Component/ComponentViewerInterface'
export * from './Device/DesktopDeviceInterface' export * from './Device/DesktopDeviceInterface'
export * from './Device/DesktopManagerInterface'
export * from './Device/DesktopWebCommunication' export * from './Device/DesktopWebCommunication'
export * from './Device/DeviceInterface' export * from './Device/DeviceInterface'
export * from './Device/Environments' export * from './Device/Environments'
@@ -16,9 +22,17 @@ export * from './Device/MobileDeviceInterface'
export * from './Device/TypeCheck' export * from './Device/TypeCheck'
export * from './Device/WebOrDesktopDeviceInterface' export * from './Device/WebOrDesktopDeviceInterface'
export * from './Diagnostics/ServiceDiagnostics' export * from './Diagnostics/ServiceDiagnostics'
export * from './Event/ApplicationEvent'
export * from './Event/ApplicationEventCallback'
export * from './Event/EventObserver' export * from './Event/EventObserver'
export * from './Event/SyncEvent' export * from './Event/SyncEvent'
export * from './Event/SyncEventReceiver' 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 './Files/FilesApiInterface'
export * from './FileSystem/FileSystemApi' export * from './FileSystem/FileSystemApi'
export * from './Integrity/IntegrityApiInterface' export * from './Integrity/IntegrityApiInterface'
@@ -32,7 +46,9 @@ export * from './Internal/InternalEventInterface'
export * from './Internal/InternalEventPublishStrategy' export * from './Internal/InternalEventPublishStrategy'
export * from './Internal/InternalEventType' export * from './Internal/InternalEventType'
export * from './Item/ItemManagerInterface' export * from './Item/ItemManagerInterface'
export * from './Item/ItemsClientInterface'
export * from './Item/ItemsServerInterface' export * from './Item/ItemsServerInterface'
export * from './Mutator/MutatorClientInterface'
export * from './Payloads/PayloadManagerInterface' export * from './Payloads/PayloadManagerInterface'
export * from './Preferences/PreferenceServiceInterface' export * from './Preferences/PreferenceServiceInterface'
export * from './Service/AbstractService' export * from './Service/AbstractService'

View File

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

View File

@@ -1,72 +1,6 @@
import { SyncEvent } from '@standardnotes/services' import { ApplicationEvent, SyncEvent } from '@standardnotes/services'
export { SyncEvent } 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) { export function applicationEventForSyncEvent(syncEvent: SyncEvent) {
return ( return (
{ {

View File

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

View File

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

View File

@@ -10,10 +10,15 @@ import {
FindNativeFeature, FindNativeFeature,
FeatureIdentifier, FeatureIdentifier,
} from '@standardnotes/features' } from '@standardnotes/features'
import { DesktopManagerInterface } from '@Lib/Services/ComponentManager/Types'
import { ContentType } from '@standardnotes/common' import { ContentType } from '@standardnotes/common'
import { GenericItem, SNComponent } from '@standardnotes/models' 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 { ItemManager } from '@Lib/Services/Items/ItemManager'
import { SNFeaturesService } from '@Lib/Services/Features/FeaturesService' import { SNFeaturesService } from '@Lib/Services/Features/FeaturesService'
import { SNComponentManager } from './ComponentManager' import { SNComponentManager } from './ComponentManager'
@@ -34,7 +39,10 @@ describe('featuresService', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
syncComponentsInstallation() {}, syncComponentsInstallation() {},
// eslint-disable-next-line @typescript-eslint/no-empty-function // 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() { getExtServerHost() {
return desktopExtHost return desktopExtHost
}, },

View File

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

View File

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

View File

@@ -1,24 +1,8 @@
import { import { ComponentArea, ComponentAction, FeatureIdentifier, LegacyFileSafeIdentifier } from '@standardnotes/features'
ComponentArea, import { ComponentMessage, ItemContent, MessageData } from '@standardnotes/models'
ComponentAction,
ComponentPermission,
FeatureIdentifier,
LegacyFileSafeIdentifier,
} from '@standardnotes/features'
import { ItemContent, SNComponent, DecryptedTransferPayload } from '@standardnotes/models'
import { UuidString } from '@Lib/Types/UuidString' import { UuidString } from '@Lib/Types/UuidString'
import { ContentType } from '@standardnotes/common' 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 = { export type OutgoingItemMessagePayload = {
uuid: string uuid: string
content_type: ContentType content_type: ContentType
@@ -63,46 +47,6 @@ export type StreamObserver = {
contentTypes?: ContentType[] 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 = { export type MessageReplyData = {
approved?: boolean approved?: boolean
deleted?: boolean deleted?: boolean
@@ -120,13 +64,6 @@ export type DeleteItemsMessageData = MessageData & {
items: OutgoingItemMessagePayload[] items: OutgoingItemMessagePayload[]
} }
export type ComponentMessage = {
action: ComponentAction
sessionKey?: string
componentData?: Record<string, unknown>
data: MessageData
}
export type MessageReply = { export type MessageReply = {
action: ComponentAction action: ComponentAction
original: ComponentMessage original: ComponentMessage

View File

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

View File

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

View File

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

View File

@@ -1,2 +1 @@
export * from './MutatorClientInterface'
export * from './MutatorService' 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 { sanitizeHtmlString } from '@standardnotes/utils'
import { SKAlert } from '@standardnotes/styles' import { SKAlert } from '@standardnotes/styles'
@@ -65,37 +64,3 @@ export function alertDialog({
alert.present() 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 { parseFileName } from '@standardnotes/filepicker'
import { import {
BackupFile, BackupFile,
@@ -6,6 +5,7 @@ import {
BackupFileDecryptedContextualPayload, BackupFileDecryptedContextualPayload,
NoteContent, NoteContent,
EncryptedItemInterface, EncryptedItemInterface,
SNApplication,
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
function sanitizeFileName(name: string): string { function sanitizeFileName(name: string): string {
@@ -27,10 +27,10 @@ type ZippableData = {
type ObjectURL = string type ObjectURL = string
export class ArchiveManager { export class ArchiveManager {
private readonly application: WebApplication private readonly application: SNApplication
private textFile?: string private textFile?: string
constructor(application: WebApplication) { constructor(application: SNApplication) {
this.application = application this.application = application
} }

View File

@@ -1,5 +1,3 @@
import { useCallback, useState } from 'react'
export enum StorageKey { export enum StorageKey {
AnonymousUserId = 'AnonymousUserId', AnonymousUserId = 'AnonymousUserId',
ShowBetaWarning = 'ShowBetaWarning', ShowBetaWarning = 'ShowBetaWarning',
@@ -26,19 +24,3 @@ export const storage = {
localStorage.removeItem(key) 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 { import {
StorageValueModes, StorageValueModes,
ApplicationService, ApplicationService,
@@ -13,6 +12,7 @@ import {
InternalEventBus, InternalEventBus,
PayloadEmitSource, PayloadEmitSource,
LocalStorageDecryptedContextualPayload, LocalStorageDecryptedContextualPayload,
WebApplicationInterface,
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
import { dismissToast, ToastType, addTimedToast } from '@standardnotes/toast' import { dismissToast, ToastType, addTimedToast } from '@standardnotes/toast'
@@ -26,7 +26,7 @@ export class ThemeManager extends ApplicationService {
private unregisterStream!: () => void private unregisterStream!: () => void
private lastUseDeviceThemeSettings = false private lastUseDeviceThemeSettings = false
constructor(application: WebApplication) { constructor(application: WebApplicationInterface) {
super(application, new InternalEventBus()) super(application, new InternalEventBus())
this.colorSchemeEventHandler = this.colorSchemeEventHandler.bind(this) this.colorSchemeEventHandler = this.colorSchemeEventHandler.bind(this)
} }
@@ -80,7 +80,7 @@ export class ThemeManager extends ApplicationService {
} }
get webApplication() { get webApplication() {
return this.application as WebApplication return this.application as WebApplicationInterface
} }
override deinit() { 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/snjs": "workspace:*",
"@standardnotes/styles": "workspace:*", "@standardnotes/styles": "workspace:*",
"@standardnotes/toast": "workspace:*", "@standardnotes/toast": "workspace:*",
"@standardnotes/ui-services": "workspace:^",
"@zip.js/zip.js": "^2.4.10", "@zip.js/zip.js": "^2.4.10",
"mobx": "^6.5.0", "mobx": "^6.5.0",
"mobx-react-lite": "^3.3.0", "mobx-react-lite": "^3.3.0",

View File

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

View File

@@ -6,14 +6,12 @@ import {
InternalEventBus, InternalEventBus,
isDesktopDevice, isDesktopDevice,
} from '@standardnotes/snjs' } 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 { 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 { WebOrDesktopDevice } from '@/Application/Device/WebOrDesktopDevice'
import { DesktopManager } from './Device/DesktopManager'
const createApplication = ( const createApplication = (
descriptor: ApplicationDescriptor, descriptor: ApplicationDescriptor,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,9 @@
import { ApplicationGroup } from '@/Application/ApplicationGroup' import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { getPlatformString, getWindowUrlParams } from '@/Utils' 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 { 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 { WebApplication } from '@/Application/Application'
import { WebAppEvent } from '@/Application/WebAppEvent'
import Navigation from '@/Components/Navigation/Navigation' import Navigation from '@/Components/Navigation/Navigation'
import NoteGroupView from '@/Components/NoteGroupView/NoteGroupView' import NoteGroupView from '@/Components/NoteGroupView/NoteGroupView'
import Footer from '@/Components/Footer/Footer' import Footer from '@/Components/Footer/Footer'

View File

@@ -1,5 +1,5 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants' 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 { formatSizeToReadableString } from '@standardnotes/filepicker'
import { FileItem } from '@standardnotes/snjs' import { FileItem } from '@standardnotes/snjs'
import { import {

View File

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

View File

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

View File

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

View File

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

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