internal: incomplete vault systems behind feature flag (#2340)
This commit is contained in:
@@ -6,4 +6,9 @@ export interface ContextPayload<C extends ItemContent = ItemContent> {
|
||||
content_type: ContentType
|
||||
content: C | string | undefined
|
||||
deleted: boolean
|
||||
|
||||
user_uuid?: string
|
||||
key_system_identifier?: string | undefined
|
||||
shared_vault_uuid?: string | undefined
|
||||
last_edited_by_uuid?: string
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export interface FilteredServerItem extends ServerItemResponse {
|
||||
__passed_filter__: true
|
||||
}
|
||||
|
||||
export function CreateFilteredServerItem(item: ServerItemResponse): FilteredServerItem {
|
||||
function CreateFilteredServerItem(item: ServerItemResponse): FilteredServerItem {
|
||||
return {
|
||||
...item,
|
||||
__passed_filter__: true,
|
||||
|
||||
@@ -19,6 +19,8 @@ export function CreateEncryptedBackupFileContextPayload(
|
||||
updated_at_timestamp: fromPayload.updated_at_timestamp,
|
||||
updated_at: fromPayload.updated_at,
|
||||
uuid: fromPayload.uuid,
|
||||
key_system_identifier: fromPayload.key_system_identifier,
|
||||
shared_vault_uuid: fromPayload.shared_vault_uuid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,5 +37,7 @@ export function CreateDecryptedBackupFileContextPayload(
|
||||
updated_at_timestamp: fromPayload.updated_at_timestamp,
|
||||
updated_at: fromPayload.updated_at,
|
||||
uuid: fromPayload.uuid,
|
||||
key_system_identifier: fromPayload.key_system_identifier,
|
||||
shared_vault_uuid: fromPayload.shared_vault_uuid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ItemContent } from '../Content/ItemContent'
|
||||
import { DecryptedPayloadInterface, DeletedPayloadInterface, EncryptedPayloadInterface } from '../Payload'
|
||||
import { useBoolean } from '@standardnotes/utils'
|
||||
import { EncryptedTransferPayload, isEncryptedTransferPayload } from '../TransferPayload'
|
||||
import { PersistentSignatureData } from '../../Runtime/Encryption/PersistentSignatureData'
|
||||
|
||||
export function isEncryptedLocalStoragePayload(
|
||||
p: LocalStorageEncryptedContextualPayload | LocalStorageDecryptedContextualPayload,
|
||||
@@ -25,6 +26,7 @@ export interface LocalStorageEncryptedContextualPayload extends ContextPayload {
|
||||
updated_at_timestamp: number
|
||||
updated_at: Date
|
||||
waitingForKey: boolean
|
||||
signatureData?: PersistentSignatureData
|
||||
}
|
||||
|
||||
export interface LocalStorageDecryptedContextualPayload<C extends ItemContent = ItemContent> extends ContextPayload {
|
||||
@@ -36,6 +38,7 @@ export interface LocalStorageDecryptedContextualPayload<C extends ItemContent =
|
||||
duplicate_of?: string
|
||||
updated_at_timestamp: number
|
||||
updated_at: Date
|
||||
signatureData?: PersistentSignatureData
|
||||
}
|
||||
|
||||
export interface LocalStorageDeletedContextualPayload extends ContextPayload {
|
||||
@@ -47,6 +50,7 @@ export interface LocalStorageDeletedContextualPayload extends ContextPayload {
|
||||
duplicate_of?: string
|
||||
updated_at_timestamp: number
|
||||
updated_at: Date
|
||||
signatureData?: PersistentSignatureData
|
||||
}
|
||||
|
||||
export function CreateEncryptedLocalStorageContextPayload(
|
||||
@@ -68,6 +72,11 @@ export function CreateEncryptedLocalStorageContextPayload(
|
||||
updated_at: fromPayload.updated_at,
|
||||
uuid: fromPayload.uuid,
|
||||
waitingForKey: fromPayload.waitingForKey,
|
||||
user_uuid: fromPayload.user_uuid,
|
||||
key_system_identifier: fromPayload.key_system_identifier,
|
||||
shared_vault_uuid: fromPayload.shared_vault_uuid,
|
||||
last_edited_by_uuid: fromPayload.last_edited_by_uuid,
|
||||
signatureData: fromPayload.signatureData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +94,11 @@ export function CreateDecryptedLocalStorageContextPayload(
|
||||
updated_at: fromPayload.updated_at,
|
||||
uuid: fromPayload.uuid,
|
||||
dirty: useBoolean(fromPayload.dirty, false),
|
||||
user_uuid: fromPayload.user_uuid,
|
||||
key_system_identifier: fromPayload.key_system_identifier,
|
||||
shared_vault_uuid: fromPayload.shared_vault_uuid,
|
||||
last_edited_by_uuid: fromPayload.last_edited_by_uuid,
|
||||
signatureData: fromPayload.signatureData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,5 +116,10 @@ export function CreateDeletedLocalStorageContextPayload(
|
||||
updated_at_timestamp: fromPayload.updated_at_timestamp,
|
||||
updated_at: fromPayload.updated_at,
|
||||
uuid: fromPayload.uuid,
|
||||
user_uuid: fromPayload.user_uuid,
|
||||
key_system_identifier: fromPayload.key_system_identifier,
|
||||
shared_vault_uuid: fromPayload.shared_vault_uuid,
|
||||
last_edited_by_uuid: fromPayload.last_edited_by_uuid,
|
||||
signatureData: fromPayload.signatureData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ export function CreateEncryptedServerSyncPushPayload(
|
||||
enc_item_key: fromPayload.enc_item_key,
|
||||
items_key_id: fromPayload.items_key_id,
|
||||
auth_hash: fromPayload.auth_hash,
|
||||
key_system_identifier: fromPayload.key_system_identifier,
|
||||
shared_vault_uuid: fromPayload.shared_vault_uuid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,5 +47,7 @@ export function CreateDeletedServerSyncPushPayload(
|
||||
updated_at: fromPayload.updated_at,
|
||||
uuid: fromPayload.uuid,
|
||||
content: undefined,
|
||||
key_system_identifier: fromPayload.key_system_identifier,
|
||||
shared_vault_uuid: fromPayload.shared_vault_uuid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ export interface ServerSyncSavedContextualPayload {
|
||||
updated_at_timestamp: number
|
||||
updated_at: Date
|
||||
uuid: string
|
||||
key_system_identifier: string | undefined
|
||||
shared_vault_uuid: string | undefined
|
||||
user_uuid: string
|
||||
duplicate_of?: string
|
||||
last_edited_by_uuid?: string
|
||||
}
|
||||
|
||||
export function CreateServerSyncSavedPayload(from: FilteredServerItem): ServerSyncSavedContextualPayload {
|
||||
@@ -27,5 +32,10 @@ export function CreateServerSyncSavedPayload(from: FilteredServerItem): ServerSy
|
||||
updated_at_timestamp: from.updated_at_timestamp,
|
||||
updated_at: from.updated_at,
|
||||
uuid: from.uuid,
|
||||
key_system_identifier: from.key_system_identifier,
|
||||
shared_vault_uuid: from.shared_vault_uuid,
|
||||
user_uuid: from.user_uuid,
|
||||
duplicate_of: from.duplicate_of,
|
||||
last_edited_by_uuid: from.last_edited_by_uuid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ConflictParams } from '@standardnotes/responses'
|
||||
import { FilteredServerItem } from './FilteredServerItem'
|
||||
|
||||
export type TrustedConflictParams = ConflictParams<FilteredServerItem>
|
||||
@@ -50,7 +50,7 @@ export class DecryptedItem<C extends ItemContent = ItemContent>
|
||||
return this.payload.content.references || []
|
||||
}
|
||||
|
||||
public isReferencingItem(item: DecryptedItemInterface): boolean {
|
||||
public isReferencingItem(item: { uuid: string }): boolean {
|
||||
return this.references.find((r) => r.uuid === item.uuid) != undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SingletonStrategy } from '../Types/SingletonStrategy'
|
||||
import { PayloadInterface } from '../../Payload/Interfaces/PayloadInterface'
|
||||
import { HistoryEntryInterface } from '../../../Runtime/History/HistoryEntryInterface'
|
||||
import { isDecryptedItem, isDeletedItem, isEncryptedErroredItem } from '../Interfaces/TypeCheck'
|
||||
import { PersistentSignatureData } from '../../../Runtime/Encryption/PersistentSignatureData'
|
||||
|
||||
export abstract class GenericItem<P extends PayloadInterface = PayloadInterface> implements ItemInterface<P> {
|
||||
payload: P
|
||||
@@ -43,6 +44,26 @@ export abstract class GenericItem<P extends PayloadInterface = PayloadInterface>
|
||||
return this.payload.created_at
|
||||
}
|
||||
|
||||
get key_system_identifier(): string | undefined {
|
||||
return this.payload.key_system_identifier
|
||||
}
|
||||
|
||||
get user_uuid(): string | undefined {
|
||||
return this.payload.user_uuid
|
||||
}
|
||||
|
||||
get shared_vault_uuid(): string | undefined {
|
||||
return this.payload.shared_vault_uuid
|
||||
}
|
||||
|
||||
get last_edited_by_uuid(): string | undefined {
|
||||
return this.payload.last_edited_by_uuid
|
||||
}
|
||||
|
||||
get signatureData(): PersistentSignatureData | undefined {
|
||||
return this.payload.signatureData
|
||||
}
|
||||
|
||||
/**
|
||||
* The date timestamp the server set for this item upon it being synced
|
||||
* Undefined if never synced to a remote server.
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface DecryptedItemInterface<C extends ItemContent = ItemContent>
|
||||
|
||||
payloadRepresentation(override?: Partial<DecryptedTransferPayload<C>>): DecryptedPayloadInterface<C>
|
||||
|
||||
isReferencingItem(item: DecryptedItemInterface): boolean
|
||||
isReferencingItem(item: { uuid: string }): boolean
|
||||
|
||||
getDomainData(domain: typeof ComponentDataDomain | typeof DefaultAppDomain): undefined | Record<string, unknown>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PredicateInterface } from '../../../Runtime/Predicate/Interface'
|
||||
import { HistoryEntryInterface } from '../../../Runtime/History'
|
||||
import { ConflictStrategy } from '../Types/ConflictStrategy'
|
||||
import { SingletonStrategy } from '../Types/SingletonStrategy'
|
||||
import { PersistentSignatureData } from '../../../Runtime/Encryption/PersistentSignatureData'
|
||||
|
||||
export interface ItemInterface<P extends PayloadInterface = PayloadInterface> {
|
||||
payload: P
|
||||
@@ -14,6 +15,11 @@ export interface ItemInterface<P extends PayloadInterface = PayloadInterface> {
|
||||
readonly updatedAtString?: string
|
||||
|
||||
uuid: string
|
||||
get key_system_identifier(): string | undefined
|
||||
get user_uuid(): string | undefined
|
||||
get shared_vault_uuid(): string | undefined
|
||||
get last_edited_by_uuid(): string | undefined
|
||||
get signatureData(): PersistentSignatureData | undefined
|
||||
|
||||
content_type: ContentType
|
||||
created_at: Date
|
||||
|
||||
@@ -10,13 +10,13 @@ import { DecryptedPayloadInterface } from '../../Payload/Interfaces/DecryptedPay
|
||||
import { ItemInterface } from '../Interfaces/ItemInterface'
|
||||
import { getIncrementedDirtyIndex } from '../../../Runtime/DirtyCounter/DirtyCounter'
|
||||
|
||||
export class DecryptedItemMutator<C extends ItemContent = ItemContent> extends ItemMutator<
|
||||
DecryptedPayloadInterface<C>,
|
||||
DecryptedItemInterface<C>
|
||||
> {
|
||||
export class DecryptedItemMutator<
|
||||
C extends ItemContent = ItemContent,
|
||||
I extends DecryptedItemInterface<C> = DecryptedItemInterface<C>,
|
||||
> extends ItemMutator<DecryptedPayloadInterface<C>, I> {
|
||||
protected mutableContent: C
|
||||
|
||||
constructor(item: DecryptedItemInterface<C>, type: MutationType) {
|
||||
constructor(item: I, type: MutationType) {
|
||||
super(item, type)
|
||||
|
||||
const mutableCopy = Copy(this.immutablePayload.content)
|
||||
@@ -43,6 +43,8 @@ export class DecryptedItemMutator<C extends ItemContent = ItemContent> extends I
|
||||
content: this.mutableContent,
|
||||
dirty: true,
|
||||
dirtyIndex: getIncrementedDirtyIndex(),
|
||||
signatureData: undefined,
|
||||
last_edited_by_uuid: undefined,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -3,6 +3,8 @@ import { PayloadInterface } from '../../Payload'
|
||||
import { ItemInterface } from '../Interfaces/ItemInterface'
|
||||
import { TransferPayload } from '../../TransferPayload'
|
||||
import { getIncrementedDirtyIndex } from '../../../Runtime/DirtyCounter/DirtyCounter'
|
||||
import { KeySystemIdentifier } from '../../../Syncable/KeySystemRootKey/KeySystemIdentifier'
|
||||
import { ContentTypeUsesRootKeyEncryption } from '../../../Runtime/Encryption/ContentTypeUsesRootKeyEncryption'
|
||||
|
||||
/**
|
||||
* An item mutator takes in an item, and an operation, and returns the resulting payload.
|
||||
@@ -51,6 +53,26 @@ export class ItemMutator<
|
||||
})
|
||||
}
|
||||
|
||||
public set key_system_identifier(keySystemIdentifier: KeySystemIdentifier | undefined) {
|
||||
if (ContentTypeUsesRootKeyEncryption(this.immutableItem.content_type)) {
|
||||
throw new Error('Cannot set key_system_identifier on a root key encrypted item')
|
||||
}
|
||||
|
||||
this.immutablePayload = this.immutablePayload.copy({
|
||||
key_system_identifier: keySystemIdentifier,
|
||||
})
|
||||
}
|
||||
|
||||
public set shared_vault_uuid(sharedVaultUuid: string | undefined) {
|
||||
if (ContentTypeUsesRootKeyEncryption(this.immutableItem.content_type)) {
|
||||
throw new Error('Cannot set shared_vault_uuid on a root key encrypted item')
|
||||
}
|
||||
|
||||
this.immutablePayload = this.immutablePayload.copy({
|
||||
shared_vault_uuid: sharedVaultUuid,
|
||||
})
|
||||
}
|
||||
|
||||
public set errorDecrypting(_: boolean) {
|
||||
throw Error('This method is no longer implemented')
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { PayloadSource } from '../Types/PayloadSource'
|
||||
import { TransferPayload } from '../../TransferPayload/Interfaces/TransferPayload'
|
||||
import { ItemContent } from '../../Content/ItemContent'
|
||||
import { SyncResolvedParams, SyncResolvedPayload } from '../../../Runtime/Deltas/Utilities/SyncResolvedPayload'
|
||||
import { PersistentSignatureData } from '../../../Runtime/Encryption/PersistentSignatureData'
|
||||
import { ContentTypeUsesRootKeyEncryption } from '../../../Runtime/Encryption/ContentTypeUsesRootKeyEncryption'
|
||||
|
||||
type RequiredKeepUndefined<T> = { [K in keyof T]-?: [T[K]] } extends infer U
|
||||
? U extends Record<keyof U, [unknown]>
|
||||
@@ -33,18 +35,28 @@ export abstract class PurePayload<T extends TransferPayload<C>, C extends ItemCo
|
||||
readonly lastSyncEnd?: Date
|
||||
|
||||
readonly duplicate_of?: string
|
||||
readonly user_uuid?: string
|
||||
readonly key_system_identifier?: string | undefined
|
||||
readonly shared_vault_uuid?: string | undefined
|
||||
readonly last_edited_by_uuid?: string
|
||||
|
||||
readonly signatureData?: PersistentSignatureData
|
||||
|
||||
constructor(rawPayload: T, source = PayloadSource.Constructor) {
|
||||
this.source = source
|
||||
this.uuid = rawPayload.uuid
|
||||
|
||||
if (!this.uuid) {
|
||||
if (!rawPayload.uuid) {
|
||||
throw Error(
|
||||
`Attempting to construct payload with null uuid
|
||||
Content type: ${rawPayload.content_type}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (rawPayload.key_system_identifier && ContentTypeUsesRootKeyEncryption(rawPayload.content_type)) {
|
||||
throw new Error('Rootkey-encrypted payload should not have a key system identifier')
|
||||
}
|
||||
|
||||
this.source = source
|
||||
this.uuid = rawPayload.uuid
|
||||
|
||||
this.content = rawPayload.content
|
||||
this.content_type = rawPayload.content_type
|
||||
this.deleted = useBoolean(rawPayload.deleted, false)
|
||||
@@ -63,6 +75,13 @@ export abstract class PurePayload<T extends TransferPayload<C>, C extends ItemCo
|
||||
this.dirtyIndex = rawPayload.dirtyIndex
|
||||
this.globalDirtyIndexAtLastSync = rawPayload.globalDirtyIndexAtLastSync
|
||||
|
||||
this.user_uuid = rawPayload.user_uuid ?? undefined
|
||||
this.key_system_identifier = rawPayload.key_system_identifier ?? undefined
|
||||
this.shared_vault_uuid = rawPayload.shared_vault_uuid ?? undefined
|
||||
this.last_edited_by_uuid = rawPayload.last_edited_by_uuid ?? undefined
|
||||
|
||||
this.signatureData = rawPayload.signatureData
|
||||
|
||||
const timeToAllowSubclassesToFinishConstruction = 0
|
||||
setTimeout(() => {
|
||||
deepFreeze(this)
|
||||
@@ -85,6 +104,11 @@ export abstract class PurePayload<T extends TransferPayload<C>, C extends ItemCo
|
||||
globalDirtyIndexAtLastSync: this.globalDirtyIndexAtLastSync,
|
||||
lastSyncBegan: this.lastSyncBegan,
|
||||
lastSyncEnd: this.lastSyncEnd,
|
||||
key_system_identifier: this.key_system_identifier,
|
||||
user_uuid: this.user_uuid,
|
||||
shared_vault_uuid: this.shared_vault_uuid,
|
||||
last_edited_by_uuid: this.last_edited_by_uuid,
|
||||
signatureData: this.signatureData,
|
||||
}
|
||||
|
||||
return comprehensive
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ContentType } from '@standardnotes/common'
|
||||
import { ItemContent } from '../../Content/ItemContent'
|
||||
import { TransferPayload } from '../../TransferPayload/Interfaces/TransferPayload'
|
||||
import { PayloadSource } from '../Types/PayloadSource'
|
||||
import { PersistentSignatureData } from '../../../Runtime/Encryption/PersistentSignatureData'
|
||||
|
||||
export interface PayloadInterface<T extends TransferPayload = TransferPayload, C extends ItemContent = ItemContent> {
|
||||
readonly source: PayloadSource
|
||||
@@ -22,12 +23,18 @@ export interface PayloadInterface<T extends TransferPayload = TransferPayload, C
|
||||
readonly dirtyIndex?: number
|
||||
readonly globalDirtyIndexAtLastSync?: number
|
||||
readonly dirty?: boolean
|
||||
readonly signatureData?: PersistentSignatureData
|
||||
|
||||
readonly lastSyncBegan?: Date
|
||||
readonly lastSyncEnd?: Date
|
||||
|
||||
readonly duplicate_of?: string
|
||||
|
||||
readonly user_uuid?: string
|
||||
readonly key_system_identifier?: string | undefined
|
||||
readonly shared_vault_uuid?: string | undefined
|
||||
readonly last_edited_by_uuid?: string
|
||||
|
||||
/**
|
||||
* "Ejected" means a payload for
|
||||
* generic, non-contextual consumption, such as saving to a backup file or syncing
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { PayloadInterface } from './../Interfaces/PayloadInterface'
|
||||
import { VaultListingInterface } from '../../../Syncable/VaultListing/VaultListingInterface'
|
||||
|
||||
export function PayloadVaultOverrides(
|
||||
vault: VaultListingInterface | undefined,
|
||||
): Pick<PayloadInterface, 'key_system_identifier' | 'shared_vault_uuid'> {
|
||||
if (!vault) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
key_system_identifier: vault.systemIdentifier,
|
||||
shared_vault_uuid: vault.isSharedVaultListing() ? vault.sharing.sharedVaultUuid : undefined,
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ export enum PayloadSource {
|
||||
*/
|
||||
Constructor = 1,
|
||||
|
||||
LocalDatabaseLoaded = 2,
|
||||
|
||||
RemoteRetrieved,
|
||||
|
||||
RemoteSaved,
|
||||
|
||||
@@ -10,4 +10,5 @@ export * from './Interfaces/TypeCheck'
|
||||
export * from './Interfaces/UnionTypes'
|
||||
export * from './Types/PayloadSource'
|
||||
export * from './Types/EmitSource'
|
||||
export * from './Types/TimestampDefaults'
|
||||
export * from './Overrides/TimestampDefaults'
|
||||
export * from './Overrides/VaultOverride'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemContent } from '../../Content/ItemContent'
|
||||
import { PersistentSignatureData } from '../../../Runtime/Encryption/PersistentSignatureData'
|
||||
|
||||
export interface TransferPayload<C extends ItemContent = ItemContent> {
|
||||
uuid: string
|
||||
@@ -15,9 +16,16 @@ export interface TransferPayload<C extends ItemContent = ItemContent> {
|
||||
dirtyIndex?: number
|
||||
globalDirtyIndexAtLastSync?: number
|
||||
dirty?: boolean
|
||||
signatureData?: PersistentSignatureData
|
||||
|
||||
lastSyncBegan?: Date
|
||||
lastSyncEnd?: Date
|
||||
|
||||
duplicate_of?: string
|
||||
user_uuid?: string
|
||||
|
||||
key_system_identifier?: string | undefined
|
||||
shared_vault_uuid?: string | undefined
|
||||
|
||||
last_edited_by_uuid?: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { KeySystemIdentifier } from '../../Syncable/KeySystemRootKey/KeySystemIdentifier'
|
||||
import { KeySystemRootKeyPasswordType } from './KeySystemRootKeyPasswordType'
|
||||
|
||||
/**
|
||||
* Key params are public data that contain information about how a root key was created.
|
||||
* Given a keyParams object and a password, clients can compute a root key that was created
|
||||
* previously.
|
||||
*/
|
||||
export interface KeySystemRootKeyParamsInterface {
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
seed: string
|
||||
version: ProtocolVersion
|
||||
passwordType: KeySystemRootKeyPasswordType
|
||||
creationTimestamp: number
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum KeySystemRootKeyPasswordType {
|
||||
UserInputted = 'user_inputted',
|
||||
Randomized = 'randomized',
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApplicationIdentifier, ProtocolVersion } from '@standardnotes/common'
|
||||
import { RootKeyContentSpecialized } from './RootKeyContent'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
|
||||
export type RawKeychainValue = Record<ApplicationIdentifier, NamespacedRootKeyInKeychain>
|
||||
|
||||
@@ -7,6 +8,8 @@ export interface NamespacedRootKeyInKeychain {
|
||||
version: ProtocolVersion
|
||||
masterKey: string
|
||||
dataAuthenticationKey?: string
|
||||
encryptionKeyPair: PkcKeyPair | undefined
|
||||
signingKeyPair: PkcKeyPair | undefined
|
||||
}
|
||||
|
||||
export type RootKeyContentInStorage = RootKeyContentSpecialized
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { ProtocolVersion, AnyKeyParamsContent } from '@standardnotes/common'
|
||||
|
||||
@@ -7,6 +8,9 @@ export interface RootKeyContentSpecialized {
|
||||
serverPassword?: string
|
||||
dataAuthenticationKey?: string
|
||||
keyParams: AnyKeyParamsContent
|
||||
|
||||
encryptionKeyPair?: PkcKeyPair
|
||||
signingKeyPair?: PkcKeyPair
|
||||
}
|
||||
|
||||
export type RootKeyContent = RootKeyContentSpecialized & ItemContent
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { RootKeyParamsInterface } from '../KeyParams/RootKeyParamsInterface'
|
||||
@@ -6,11 +7,16 @@ import { RootKeyContent } from './RootKeyContent'
|
||||
|
||||
export interface RootKeyInterface extends DecryptedItemInterface<RootKeyContent> {
|
||||
readonly keyParams: RootKeyParamsInterface
|
||||
|
||||
get keyVersion(): ProtocolVersion
|
||||
get itemsKey(): string
|
||||
get masterKey(): string
|
||||
get serverPassword(): string | undefined
|
||||
get dataAuthenticationKey(): string | undefined
|
||||
|
||||
get encryptionKeyPair(): PkcKeyPair | undefined
|
||||
get signingKeyPair(): PkcKeyPair | undefined
|
||||
|
||||
compare(otherKey: RootKeyInterface): boolean
|
||||
persistableValueWhenWrapping(): RootKeyContentInStorage
|
||||
getKeychainValue(): NamespacedRootKeyInKeychain
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { RootKeyInterface } from './RootKeyInterface'
|
||||
|
||||
export interface RootKeyWithKeyPairsInterface extends RootKeyInterface {
|
||||
get encryptionKeyPair(): PkcKeyPair
|
||||
get signingKeyPair(): PkcKeyPair
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type AsymmetricMessageDataCommon = {
|
||||
recipientUuid: string
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { AsymmetricMessageSenderKeypairChanged } from './MessageTypes/AsymmetricMessageSenderKeypairChanged'
|
||||
import { AsymmetricMessageSharedVaultInvite } from './MessageTypes/AsymmetricMessageSharedVaultInvite'
|
||||
import { AsymmetricMessageSharedVaultMetadataChanged } from './MessageTypes/AsymmetricMessageSharedVaultMetadataChanged'
|
||||
import { AsymmetricMessageSharedVaultRootKeyChanged } from './MessageTypes/AsymmetricMessageSharedVaultRootKeyChanged'
|
||||
import { AsymmetricMessageTrustedContactShare } from './MessageTypes/AsymmetricMessageTrustedContactShare'
|
||||
|
||||
export type AsymmetricMessagePayload =
|
||||
| AsymmetricMessageSharedVaultRootKeyChanged
|
||||
| AsymmetricMessageTrustedContactShare
|
||||
| AsymmetricMessageSenderKeypairChanged
|
||||
| AsymmetricMessageSharedVaultInvite
|
||||
| AsymmetricMessageSharedVaultMetadataChanged
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum AsymmetricMessagePayloadType {
|
||||
ContactShare = 'contact-share',
|
||||
SharedVaultRootKeyChanged = 'shared-vault-root-key-changed',
|
||||
SenderKeypairChanged = 'sender-keypair-changed',
|
||||
SharedVaultInvite = 'shared-vault-invite',
|
||||
SharedVaultMetadataChanged = 'shared-vault-metadata-changed',
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon'
|
||||
import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType'
|
||||
|
||||
export type AsymmetricMessageSenderKeypairChanged = {
|
||||
type: AsymmetricMessagePayloadType.SenderKeypairChanged
|
||||
data: AsymmetricMessageDataCommon & {
|
||||
newEncryptionPublicKey: string
|
||||
newSigningPublicKey: string
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { KeySystemRootKeyContentSpecialized } from '../../../Syncable/KeySystemRootKey/KeySystemRootKeyContent'
|
||||
import { TrustedContactContentSpecialized } from '../../../Syncable/TrustedContact/TrustedContactContent'
|
||||
import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon'
|
||||
import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType'
|
||||
|
||||
export type AsymmetricMessageSharedVaultInvite = {
|
||||
type: AsymmetricMessagePayloadType.SharedVaultInvite
|
||||
data: AsymmetricMessageDataCommon & {
|
||||
rootKey: KeySystemRootKeyContentSpecialized
|
||||
trustedContacts: TrustedContactContentSpecialized[]
|
||||
metadata: {
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon'
|
||||
import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType'
|
||||
|
||||
export type AsymmetricMessageSharedVaultMetadataChanged = {
|
||||
type: AsymmetricMessagePayloadType.SharedVaultMetadataChanged
|
||||
data: AsymmetricMessageDataCommon & {
|
||||
sharedVaultUuid: string
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { KeySystemRootKeyContentSpecialized } from '../../../Syncable/KeySystemRootKey/KeySystemRootKeyContent'
|
||||
import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon'
|
||||
import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType'
|
||||
|
||||
export type AsymmetricMessageSharedVaultRootKeyChanged = {
|
||||
type: AsymmetricMessagePayloadType.SharedVaultRootKeyChanged
|
||||
data: AsymmetricMessageDataCommon & { rootKey: KeySystemRootKeyContentSpecialized }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { TrustedContactContentSpecialized } from '../../../Syncable/TrustedContact/TrustedContactContent'
|
||||
import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon'
|
||||
import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType'
|
||||
|
||||
export type AsymmetricMessageTrustedContactShare = {
|
||||
type: AsymmetricMessagePayloadType.ContactShare
|
||||
data: AsymmetricMessageDataCommon & { trustedContact: TrustedContactContentSpecialized }
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ItemCounter } from './ItemCounter'
|
||||
import { NoteContent } from '../../../Syncable/Note/NoteContent'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { DecryptedItem, EncryptedItem } from '../../../Abstract/Item'
|
||||
import { DecryptedPayload, EncryptedPayload, PayloadTimestampDefaults } from '../../../Abstract/Payload'
|
||||
import { ItemCollection } from './ItemCollection'
|
||||
import { FillItemContent } from '../../../Abstract/Content/ItemContent'
|
||||
import { TagItemsIndex } from './TagItemsIndex'
|
||||
import { ItemDelta } from '../../Index/ItemDelta'
|
||||
import { AnyItemInterface } from '../../../Abstract/Item/Interfaces/UnionTypes'
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('tag notes index', () => {
|
||||
|
||||
it('should count both notes and files', () => {
|
||||
const collection = new ItemCollection()
|
||||
const index = new TagItemsIndex(collection)
|
||||
const index = new ItemCounter(collection)
|
||||
|
||||
const decryptedNote = createDecryptedItem('note')
|
||||
const decryptedFile = createDecryptedItem('file')
|
||||
@@ -61,7 +61,7 @@ describe('tag notes index', () => {
|
||||
|
||||
it('should decrement count after decrypted note becomes errored', () => {
|
||||
const collection = new ItemCollection()
|
||||
const index = new TagItemsIndex(collection)
|
||||
const index = new ItemCounter(collection)
|
||||
|
||||
const decryptedItem = createDecryptedItem()
|
||||
collection.set(decryptedItem)
|
||||
@@ -4,25 +4,28 @@ import { isTag, SNTag } from '../../../Syncable/Tag/Tag'
|
||||
import { SNIndex } from '../../Index/SNIndex'
|
||||
import { ItemCollection } from './ItemCollection'
|
||||
import { ItemDelta } from '../../Index/ItemDelta'
|
||||
import { isDecryptedItem, ItemInterface } from '../../../Abstract/Item'
|
||||
import { DecryptedItemInterface, isDecryptedItem, ItemInterface } from '../../../Abstract/Item'
|
||||
import { CriteriaValidatorInterface } from '../../Display/Validator/CriteriaValidatorInterface'
|
||||
import { CollectionCriteriaValidator } from '../../Display/Validator/CollectionCriteriaValidator'
|
||||
import { ExcludeVaultsCriteriaValidator } from '../../Display/Validator/ExcludeVaultsCriteriaValidator'
|
||||
import { ExclusiveVaultCriteriaValidator } from '../../Display/Validator/ExclusiveVaultCriteriaValidator'
|
||||
import { HiddenContentCriteriaValidator } from '../../Display/Validator/HiddenContentCriteriaValidator'
|
||||
import { CustomFilterCriteriaValidator } from '../../Display/Validator/CustomFilterCriteriaValidator'
|
||||
import { AnyDisplayOptions, VaultDisplayOptions } from '../../Display'
|
||||
import { isExclusioanaryOptionsValue } from '../../Display/VaultDisplayOptionsTypes'
|
||||
|
||||
type AllNotesUuidSignifier = undefined
|
||||
export type TagItemCountChangeObserver = (tagUuid: string | AllNotesUuidSignifier) => void
|
||||
|
||||
export class TagItemsIndex implements SNIndex {
|
||||
export class ItemCounter implements SNIndex {
|
||||
private tagToItemsMap: Partial<Record<string, Set<string>>> = {}
|
||||
private allCountableItems = new Set<string>()
|
||||
private countableItemsByType = new Map<ContentType, Set<string>>()
|
||||
private displayOptions?: AnyDisplayOptions
|
||||
private vaultDisplayOptions?: VaultDisplayOptions
|
||||
|
||||
constructor(private collection: ItemCollection, public observers: TagItemCountChangeObserver[] = []) {}
|
||||
|
||||
private isItemCountable = (item: ItemInterface) => {
|
||||
if (isDecryptedItem(item)) {
|
||||
return !item.archived && !item.trashed && !item.conflictOf
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public addCountChangeObserver(observer: TagItemCountChangeObserver): () => void {
|
||||
this.observers.push(observer)
|
||||
|
||||
@@ -32,10 +35,14 @@ export class TagItemsIndex implements SNIndex {
|
||||
}
|
||||
}
|
||||
|
||||
private notifyObservers(tagUuid: string | undefined) {
|
||||
for (const observer of this.observers) {
|
||||
observer(tagUuid)
|
||||
}
|
||||
public setDisplayOptions(options: AnyDisplayOptions) {
|
||||
this.displayOptions = options
|
||||
this.receiveItemChanges(this.collection.all())
|
||||
}
|
||||
|
||||
public setVaultDisplayOptions(options: VaultDisplayOptions) {
|
||||
this.vaultDisplayOptions = options
|
||||
this.receiveItemChanges(this.collection.all())
|
||||
}
|
||||
|
||||
public allCountableItemsCount(): number {
|
||||
@@ -64,6 +71,47 @@ export class TagItemsIndex implements SNIndex {
|
||||
this.receiveTagChanges(tags)
|
||||
}
|
||||
|
||||
private passesAllFilters(element: DecryptedItemInterface): boolean {
|
||||
if (!this.displayOptions) {
|
||||
return true
|
||||
}
|
||||
|
||||
const filters: CriteriaValidatorInterface[] = [new CollectionCriteriaValidator(this.collection, element)]
|
||||
|
||||
if (this.vaultDisplayOptions) {
|
||||
const options = this.vaultDisplayOptions.getOptions()
|
||||
if (isExclusioanaryOptionsValue(options)) {
|
||||
filters.push(new ExcludeVaultsCriteriaValidator([...options.exclude, ...options.locked], element))
|
||||
} else {
|
||||
filters.push(new ExclusiveVaultCriteriaValidator(options.exclusive, element))
|
||||
}
|
||||
}
|
||||
|
||||
if ('hiddenContentTypes' in this.displayOptions && this.displayOptions.hiddenContentTypes) {
|
||||
filters.push(new HiddenContentCriteriaValidator(this.displayOptions.hiddenContentTypes, element))
|
||||
}
|
||||
|
||||
if ('customFilter' in this.displayOptions && this.displayOptions.customFilter) {
|
||||
filters.push(new CustomFilterCriteriaValidator(this.displayOptions.customFilter, element))
|
||||
}
|
||||
|
||||
return filters.every((f) => f.passes())
|
||||
}
|
||||
|
||||
private isItemCountable = (item: ItemInterface) => {
|
||||
if (isDecryptedItem(item)) {
|
||||
const passesFilters = this.passesAllFilters(item)
|
||||
return passesFilters && !item.archived && !item.trashed && !item.conflictOf
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private notifyObservers(tagUuid: string | undefined) {
|
||||
for (const observer of this.observers) {
|
||||
observer(tagUuid)
|
||||
}
|
||||
}
|
||||
|
||||
private receiveTagChanges(tags: SNTag[]): void {
|
||||
for (const tag of tags) {
|
||||
const uuids = tag.references
|
||||
@@ -1,15 +1,16 @@
|
||||
import { ConflictDelta } from './Conflict'
|
||||
import { PayloadEmitSource } from '../../Abstract/Payload'
|
||||
import { FullyFormedPayloadInterface, PayloadEmitSource } from '../../Abstract/Payload'
|
||||
import { ImmutablePayloadCollection } from '../Collection/Payload/ImmutablePayloadCollection'
|
||||
import { HistoryMap } from '../History'
|
||||
import { extendSyncDelta, SyncDeltaEmit } from './Abstract/DeltaEmit'
|
||||
import { SyncDeltaInterface } from './Abstract/SyncDeltaInterface'
|
||||
import { payloadByFinalizingSyncState } from './Utilities/ApplyDirtyState'
|
||||
import { ConflictConflictingDataParams } from '@standardnotes/responses'
|
||||
|
||||
export class DeltaRemoteDataConflicts implements SyncDeltaInterface {
|
||||
constructor(
|
||||
readonly baseCollection: ImmutablePayloadCollection,
|
||||
readonly applyCollection: ImmutablePayloadCollection,
|
||||
readonly conflicts: ConflictConflictingDataParams<FullyFormedPayloadInterface>[],
|
||||
readonly historyMap: HistoryMap,
|
||||
) {}
|
||||
|
||||
@@ -20,18 +21,18 @@ export class DeltaRemoteDataConflicts implements SyncDeltaInterface {
|
||||
source: PayloadEmitSource.RemoteRetrieved,
|
||||
}
|
||||
|
||||
for (const apply of this.applyCollection.all()) {
|
||||
const base = this.baseCollection.find(apply.uuid)
|
||||
for (const conflict of this.conflicts) {
|
||||
const base = this.baseCollection.find(conflict.server_item.uuid)
|
||||
|
||||
const isBaseDeleted = base == undefined
|
||||
|
||||
if (isBaseDeleted) {
|
||||
result.emits.push(payloadByFinalizingSyncState(apply, this.baseCollection))
|
||||
result.emits.push(payloadByFinalizingSyncState(conflict.server_item, this.baseCollection))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const delta = new ConflictDelta(this.baseCollection, base, apply, this.historyMap)
|
||||
const delta = new ConflictDelta(this.baseCollection, base, conflict.server_item, this.historyMap)
|
||||
|
||||
extendSyncDelta(result, delta.result())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { FillItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { DecryptedPayload, PayloadTimestampDefaults } from '../../Abstract/Payload'
|
||||
import { DecryptedPayload, FullyFormedPayloadInterface, PayloadTimestampDefaults } from '../../Abstract/Payload'
|
||||
import { NoteContent } from '../../Syncable/Note'
|
||||
import { PayloadCollection } from '../Collection/Payload/PayloadCollection'
|
||||
import { DeltaRemoteRejected } from './RemoteRejected'
|
||||
import { ImmutablePayloadCollection } from '../Collection/Payload/ImmutablePayloadCollection'
|
||||
import { ConflictParams, ConflictType } from '@standardnotes/responses'
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
|
||||
UuidGenerator.SetGenerator(() => String(Math.random()))
|
||||
|
||||
describe('remote rejected delta', () => {
|
||||
it('rejected payloads should not map onto app state', async () => {
|
||||
@@ -30,10 +34,12 @@ describe('remote rejected delta', () => {
|
||||
dirty: true,
|
||||
})
|
||||
|
||||
const delta = new DeltaRemoteRejected(
|
||||
ImmutablePayloadCollection.FromCollection(baseCollection),
|
||||
ImmutablePayloadCollection.WithPayloads([rejectedPayload]),
|
||||
)
|
||||
const entry: ConflictParams<FullyFormedPayloadInterface> = {
|
||||
type: ConflictType.ContentTypeError,
|
||||
unsaved_item: rejectedPayload,
|
||||
} as unknown as ConflictParams<FullyFormedPayloadInterface>
|
||||
|
||||
const delta = new DeltaRemoteRejected(ImmutablePayloadCollection.FromCollection(baseCollection), [entry])
|
||||
|
||||
const result = delta.result()
|
||||
const payload = result.emits[0] as DecryptedPayload<NoteContent>
|
||||
|
||||
@@ -1,35 +1,48 @@
|
||||
import { PayloadSource } from '../../Abstract/Payload/Types/PayloadSource'
|
||||
import { PayloadEmitSource } from '../../Abstract/Payload'
|
||||
import { DeletedPayload, FullyFormedPayloadInterface, PayloadEmitSource } from '../../Abstract/Payload'
|
||||
import { ImmutablePayloadCollection } from '../Collection/Payload/ImmutablePayloadCollection'
|
||||
import { SyncDeltaEmit } from './Abstract/DeltaEmit'
|
||||
import { SyncDeltaInterface } from './Abstract/SyncDeltaInterface'
|
||||
import { SyncResolvedPayload } from './Utilities/SyncResolvedPayload'
|
||||
import { BuildSyncResolvedParams, SyncResolvedPayload } from './Utilities/SyncResolvedPayload'
|
||||
import {
|
||||
ConflictParams,
|
||||
ConflictParamsWithServerItem,
|
||||
ConflictParamsWithUnsavedItem,
|
||||
ConflictParamsWithServerAndUnsavedItem,
|
||||
conflictParamsHasServerItemAndUnsavedItem,
|
||||
conflictParamsHasOnlyServerItem,
|
||||
conflictParamsHasOnlyUnsavedItem,
|
||||
ConflictType,
|
||||
} from '@standardnotes/responses'
|
||||
import { PayloadsByDuplicating } from '../../Utilities/Payload/PayloadsByDuplicating'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
|
||||
export class DeltaRemoteRejected implements SyncDeltaInterface {
|
||||
constructor(
|
||||
readonly baseCollection: ImmutablePayloadCollection,
|
||||
readonly applyCollection: ImmutablePayloadCollection,
|
||||
readonly conflicts: ConflictParams<FullyFormedPayloadInterface>[],
|
||||
) {}
|
||||
|
||||
public result(): SyncDeltaEmit {
|
||||
const results: SyncResolvedPayload[] = []
|
||||
|
||||
for (const apply of this.applyCollection.all()) {
|
||||
const base = this.baseCollection.find(apply.uuid)
|
||||
const vaultErrors: ConflictType[] = [
|
||||
ConflictType.SharedVaultInsufficientPermissionsError,
|
||||
ConflictType.SharedVaultNotMemberError,
|
||||
ConflictType.SharedVaultInvalidState,
|
||||
ConflictType.SharedVaultSnjsVersionError,
|
||||
]
|
||||
|
||||
if (!base) {
|
||||
continue
|
||||
for (const conflict of this.conflicts) {
|
||||
if (vaultErrors.includes(conflict.type)) {
|
||||
results.push(...this.handleVaultError(conflict))
|
||||
} else if (conflictParamsHasServerItemAndUnsavedItem(conflict)) {
|
||||
results.push(...this.getResultForConflictWithServerItemAndUnsavedItem(conflict))
|
||||
} else if (conflictParamsHasOnlyServerItem(conflict)) {
|
||||
results.push(...this.getResultForConflictWithOnlyServerItem(conflict))
|
||||
} else if (conflictParamsHasOnlyUnsavedItem(conflict)) {
|
||||
results.push(...this.getResultForConflictWithOnlyUnsavedItem(conflict))
|
||||
}
|
||||
|
||||
const result = base.copyAsSyncResolved(
|
||||
{
|
||||
dirty: false,
|
||||
lastSyncEnd: new Date(),
|
||||
},
|
||||
PayloadSource.RemoteSaved,
|
||||
)
|
||||
|
||||
results.push(result)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -37,4 +50,177 @@ export class DeltaRemoteRejected implements SyncDeltaInterface {
|
||||
source: PayloadEmitSource.RemoteSaved,
|
||||
}
|
||||
}
|
||||
|
||||
private handleVaultError(conflict: ConflictParams<FullyFormedPayloadInterface>): SyncResolvedPayload[] {
|
||||
const base = this.baseCollection.find(conflict.unsaved_item.uuid)
|
||||
if (!base) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (conflict.type === ConflictType.SharedVaultNotMemberError) {
|
||||
return this.resultByDuplicatingBasePayloadAsNonVaultedAndRemovingBaseItemLocally(base)
|
||||
}
|
||||
|
||||
if (base.content_type === ContentType.KeySystemItemsKey) {
|
||||
return this.discardChangesOfBasePayload(base)
|
||||
}
|
||||
|
||||
if (conflict.server_item) {
|
||||
return this.resultByDuplicatingBasePayloadAsNonVaultedAndTakingServerPayloadAsCanonical(
|
||||
base,
|
||||
conflict.server_item,
|
||||
)
|
||||
} else {
|
||||
return this.resultByDuplicatingBasePayloadAsNonVaultedAndDiscardingChangesOfOriginal(base)
|
||||
}
|
||||
}
|
||||
|
||||
private discardChangesOfBasePayload(base: FullyFormedPayloadInterface): SyncResolvedPayload[] {
|
||||
const undirtiedPayload = base.copyAsSyncResolved(
|
||||
{
|
||||
dirty: false,
|
||||
lastSyncEnd: new Date(),
|
||||
},
|
||||
PayloadSource.RemoteSaved,
|
||||
)
|
||||
|
||||
return [undirtiedPayload]
|
||||
}
|
||||
|
||||
private getResultForConflictWithOnlyUnsavedItem(
|
||||
conflict: ConflictParamsWithUnsavedItem<FullyFormedPayloadInterface>,
|
||||
): SyncResolvedPayload[] {
|
||||
const base = this.baseCollection.find(conflict.unsaved_item.uuid)
|
||||
if (!base) {
|
||||
return []
|
||||
}
|
||||
|
||||
const result = base.copyAsSyncResolved(
|
||||
{
|
||||
dirty: false,
|
||||
lastSyncEnd: new Date(),
|
||||
},
|
||||
PayloadSource.RemoteSaved,
|
||||
)
|
||||
|
||||
return [result]
|
||||
}
|
||||
|
||||
private getResultForConflictWithOnlyServerItem(
|
||||
conflict: ConflictParamsWithServerItem<FullyFormedPayloadInterface>,
|
||||
): SyncResolvedPayload[] {
|
||||
const base = this.baseCollection.find(conflict.server_item.uuid)
|
||||
if (!base) {
|
||||
return []
|
||||
}
|
||||
|
||||
return this.resultByDuplicatingBasePayloadIntoNewUuidAndTakingServerPayloadAsCanonical(base, conflict.server_item)
|
||||
}
|
||||
|
||||
private getResultForConflictWithServerItemAndUnsavedItem(
|
||||
conflict: ConflictParamsWithServerAndUnsavedItem<FullyFormedPayloadInterface>,
|
||||
): SyncResolvedPayload[] {
|
||||
const base = this.baseCollection.find(conflict.server_item.uuid)
|
||||
if (!base) {
|
||||
return []
|
||||
}
|
||||
|
||||
return this.resultByDuplicatingBasePayloadIntoNewUuidAndTakingServerPayloadAsCanonical(base, conflict.server_item)
|
||||
}
|
||||
|
||||
private resultByDuplicatingBasePayloadIntoNewUuidAndTakingServerPayloadAsCanonical(
|
||||
basePayload: FullyFormedPayloadInterface,
|
||||
serverPayload: FullyFormedPayloadInterface,
|
||||
): SyncResolvedPayload[] {
|
||||
const duplicateBasePayloadIntoNewUuid = PayloadsByDuplicating({
|
||||
payload: basePayload,
|
||||
baseCollection: this.baseCollection,
|
||||
isConflict: true,
|
||||
source: serverPayload.source,
|
||||
})
|
||||
|
||||
const takeServerPayloadAsCanonical = serverPayload.copyAsSyncResolved(
|
||||
{
|
||||
lastSyncBegan: basePayload.lastSyncBegan,
|
||||
dirty: false,
|
||||
lastSyncEnd: new Date(),
|
||||
},
|
||||
serverPayload.source,
|
||||
)
|
||||
|
||||
return duplicateBasePayloadIntoNewUuid.concat([takeServerPayloadAsCanonical])
|
||||
}
|
||||
|
||||
private resultByDuplicatingBasePayloadAsNonVaultedAndTakingServerPayloadAsCanonical(
|
||||
basePayload: FullyFormedPayloadInterface,
|
||||
serverPayload: FullyFormedPayloadInterface,
|
||||
): SyncResolvedPayload[] {
|
||||
const duplicateBasePayloadIntoNewUuid = PayloadsByDuplicating({
|
||||
payload: basePayload.copy({
|
||||
key_system_identifier: undefined,
|
||||
shared_vault_uuid: undefined,
|
||||
}),
|
||||
baseCollection: this.baseCollection,
|
||||
isConflict: true,
|
||||
source: serverPayload.source,
|
||||
})
|
||||
|
||||
const takeServerPayloadAsCanonical = serverPayload.copyAsSyncResolved(
|
||||
{
|
||||
lastSyncBegan: basePayload.lastSyncBegan,
|
||||
dirty: false,
|
||||
lastSyncEnd: new Date(),
|
||||
},
|
||||
serverPayload.source,
|
||||
)
|
||||
|
||||
return duplicateBasePayloadIntoNewUuid.concat([takeServerPayloadAsCanonical])
|
||||
}
|
||||
|
||||
private resultByDuplicatingBasePayloadAsNonVaultedAndDiscardingChangesOfOriginal(
|
||||
basePayload: FullyFormedPayloadInterface,
|
||||
): SyncResolvedPayload[] {
|
||||
const duplicateBasePayloadWithoutVault = PayloadsByDuplicating({
|
||||
payload: basePayload.copy({
|
||||
key_system_identifier: undefined,
|
||||
shared_vault_uuid: undefined,
|
||||
}),
|
||||
baseCollection: this.baseCollection,
|
||||
isConflict: true,
|
||||
source: basePayload.source,
|
||||
})
|
||||
|
||||
return [...duplicateBasePayloadWithoutVault, ...this.discardChangesOfBasePayload(basePayload)]
|
||||
}
|
||||
|
||||
private resultByDuplicatingBasePayloadAsNonVaultedAndRemovingBaseItemLocally(
|
||||
basePayload: FullyFormedPayloadInterface,
|
||||
): SyncResolvedPayload[] {
|
||||
const duplicateBasePayloadWithoutVault = PayloadsByDuplicating({
|
||||
payload: basePayload.copy({
|
||||
key_system_identifier: undefined,
|
||||
shared_vault_uuid: undefined,
|
||||
}),
|
||||
baseCollection: this.baseCollection,
|
||||
isConflict: true,
|
||||
source: basePayload.source,
|
||||
})
|
||||
|
||||
const locallyDeletedBasePayload = new DeletedPayload(
|
||||
{
|
||||
...basePayload,
|
||||
content: undefined,
|
||||
deleted: true,
|
||||
key_system_identifier: undefined,
|
||||
shared_vault_uuid: undefined,
|
||||
...BuildSyncResolvedParams({
|
||||
dirty: false,
|
||||
lastSyncEnd: new Date(),
|
||||
}),
|
||||
},
|
||||
PayloadSource.RemoteSaved,
|
||||
)
|
||||
|
||||
return [...duplicateBasePayloadWithoutVault, locallyDeletedBasePayload as SyncResolvedPayload]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class DeltaRemoteRetrieved implements SyncDeltaInterface {
|
||||
* or if the item is locally dirty, filter it out of retrieved_items, and add to potential conflicts.
|
||||
*/
|
||||
for (const apply of this.applyCollection.all()) {
|
||||
if (apply.content_type === ContentType.ItemsKey) {
|
||||
if (apply.content_type === ContentType.ItemsKey || apply.content_type === ContentType.KeySystemItemsKey) {
|
||||
const itemsKeyDeltaEmit = new ItemsKeyDelta(this.baseCollection, [apply]).result()
|
||||
|
||||
extendSyncDelta(result, itemsKeyDeltaEmit)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ServerSyncSavedContextualPayload } from './../../Abstract/Contextual/ServerSyncSaved'
|
||||
import { ServerSyncSavedContextualPayload } from '../../Abstract/Contextual/ServerSyncSaved'
|
||||
import { DeletedPayload } from './../../Abstract/Payload/Implementations/DeletedPayload'
|
||||
import { ImmutablePayloadCollection } from '../Collection/Payload/ImmutablePayloadCollection'
|
||||
import { PayloadSource } from '../../Abstract/Payload/Types/PayloadSource'
|
||||
|
||||
@@ -2,10 +2,11 @@ import { extendArray, filterFromArray, Uuids } from '@standardnotes/utils'
|
||||
import { ImmutablePayloadCollection } from '../Collection/Payload/ImmutablePayloadCollection'
|
||||
import { PayloadsByAlternatingUuid } from '../../Utilities/Payload/PayloadsByAlternatingUuid'
|
||||
import { isDecryptedPayload } from '../../Abstract/Payload/Interfaces/TypeCheck'
|
||||
import { PayloadEmitSource } from '../../Abstract/Payload'
|
||||
import { FullyFormedPayloadInterface, PayloadEmitSource } from '../../Abstract/Payload'
|
||||
import { SyncDeltaEmit } from './Abstract/DeltaEmit'
|
||||
import { SyncDeltaInterface } from './Abstract/SyncDeltaInterface'
|
||||
import { SyncResolvedPayload } from './Utilities/SyncResolvedPayload'
|
||||
import { ConflictUuidConflictParams } from '@standardnotes/responses'
|
||||
|
||||
/**
|
||||
* UUID conflicts can occur if a user attmpts to import an old data
|
||||
@@ -15,22 +16,22 @@ import { SyncResolvedPayload } from './Utilities/SyncResolvedPayload'
|
||||
export class DeltaRemoteUuidConflicts implements SyncDeltaInterface {
|
||||
constructor(
|
||||
readonly baseCollection: ImmutablePayloadCollection,
|
||||
readonly applyCollection: ImmutablePayloadCollection,
|
||||
readonly conflicts: ConflictUuidConflictParams<FullyFormedPayloadInterface>[],
|
||||
) {}
|
||||
|
||||
public result(): SyncDeltaEmit {
|
||||
const results: SyncResolvedPayload[] = []
|
||||
const baseCollectionCopy = this.baseCollection.mutableCopy()
|
||||
|
||||
for (const apply of this.applyCollection.all()) {
|
||||
for (const conflict of this.conflicts) {
|
||||
/**
|
||||
* The payload in question may have been modified as part of alternating a uuid for
|
||||
* another item. For example, alternating a uuid for a note will also affect the
|
||||
* referencing tag, which would be added to `results`, but could also be inside
|
||||
* of this.applyCollection. In this case we'd prefer the most recently modified value.
|
||||
*/
|
||||
const moreRecent = results.find((r) => r.uuid === apply.uuid)
|
||||
const useApply = moreRecent || apply
|
||||
const moreRecent = results.find((r) => r.uuid === conflict.unsaved_item.uuid)
|
||||
const useApply = moreRecent || conflict.unsaved_item
|
||||
|
||||
if (!isDecryptedPayload(useApply)) {
|
||||
continue
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createNoteWithContent } from '../../Utilities/Test/SpecUtils'
|
||||
import { ItemCollection } from '../Collection/Item/ItemCollection'
|
||||
import { SNNote } from '../../Syncable/Note/Note'
|
||||
import { itemsMatchingOptions } from './Search/SearchUtilities'
|
||||
import { FilterDisplayOptions } from './DisplayOptions'
|
||||
import { notesAndFilesMatchingOptions } from './Search/SearchUtilities'
|
||||
import { NotesAndFilesDisplayOptions } from './DisplayOptions'
|
||||
|
||||
describe('item display options', () => {
|
||||
const collectionWithNotes = function (titles: (string | undefined)[] = [], bodies: string[] = []) {
|
||||
@@ -23,31 +23,31 @@ describe('item display options', () => {
|
||||
it('string query title', () => {
|
||||
const query = 'foo'
|
||||
|
||||
const options: FilterDisplayOptions = {
|
||||
const options: NotesAndFilesDisplayOptions = {
|
||||
searchQuery: { query: query, includeProtectedNoteText: true },
|
||||
}
|
||||
} as jest.Mocked<NotesAndFilesDisplayOptions>
|
||||
const collection = collectionWithNotes(['hello', 'fobar', 'foobar', 'foo'])
|
||||
expect(itemsMatchingOptions(options, collection.all() as SNNote[], collection)).toHaveLength(2)
|
||||
expect(notesAndFilesMatchingOptions(options, collection.all() as SNNote[], collection)).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('string query text', async function () {
|
||||
const query = 'foo'
|
||||
const options: FilterDisplayOptions = {
|
||||
const options: NotesAndFilesDisplayOptions = {
|
||||
searchQuery: { query: query, includeProtectedNoteText: true },
|
||||
}
|
||||
} as jest.Mocked<NotesAndFilesDisplayOptions>
|
||||
const collection = collectionWithNotes(
|
||||
[undefined, undefined, undefined, undefined],
|
||||
['hello', 'fobar', 'foobar', 'foo'],
|
||||
)
|
||||
expect(itemsMatchingOptions(options, collection.all() as SNNote[], collection)).toHaveLength(2)
|
||||
expect(notesAndFilesMatchingOptions(options, collection.all() as SNNote[], collection)).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('string query title and text', async function () {
|
||||
const query = 'foo'
|
||||
const options: FilterDisplayOptions = {
|
||||
const options: NotesAndFilesDisplayOptions = {
|
||||
searchQuery: { query: query, includeProtectedNoteText: true },
|
||||
}
|
||||
} as jest.Mocked<NotesAndFilesDisplayOptions>
|
||||
const collection = collectionWithNotes(['hello', 'foobar'], ['foo', 'fobar'])
|
||||
expect(itemsMatchingOptions(options, collection.all() as SNNote[], collection)).toHaveLength(2)
|
||||
expect(notesAndFilesMatchingOptions(options, collection.all() as SNNote[], collection)).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,21 +5,28 @@ import { CollectionSortDirection, CollectionSortProperty } from '../Collection/C
|
||||
import { SearchQuery } from './Search/Types'
|
||||
import { DisplayControllerCustomFilter } from './Types'
|
||||
|
||||
export type DisplayOptions = FilterDisplayOptions & DisplayControllerOptions
|
||||
|
||||
export interface FilterDisplayOptions {
|
||||
tags?: SNTag[]
|
||||
views?: SmartView[]
|
||||
searchQuery?: SearchQuery
|
||||
export interface GenericDisplayOptions {
|
||||
includePinned?: boolean
|
||||
includeProtected?: boolean
|
||||
includeTrashed?: boolean
|
||||
includeArchived?: boolean
|
||||
}
|
||||
|
||||
export interface DisplayControllerOptions {
|
||||
sortBy: CollectionSortProperty
|
||||
sortDirection: CollectionSortDirection
|
||||
export interface NotesAndFilesDisplayOptions extends GenericDisplayOptions {
|
||||
tags?: SNTag[]
|
||||
views?: SmartView[]
|
||||
searchQuery?: SearchQuery
|
||||
hiddenContentTypes?: ContentType[]
|
||||
customFilter?: DisplayControllerCustomFilter
|
||||
}
|
||||
|
||||
export type TagsDisplayOptions = GenericDisplayOptions
|
||||
|
||||
export interface DisplayControllerDisplayOptions extends GenericDisplayOptions {
|
||||
sortBy: CollectionSortProperty
|
||||
sortDirection: CollectionSortDirection
|
||||
}
|
||||
|
||||
export type NotesAndFilesDisplayControllerOptions = NotesAndFilesDisplayOptions & DisplayControllerDisplayOptions
|
||||
export type TagsDisplayControllerOptions = TagsDisplayOptions & DisplayControllerDisplayOptions
|
||||
export type AnyDisplayOptions = NotesAndFilesDisplayOptions | TagsDisplayOptions | GenericDisplayOptions
|
||||
|
||||
@@ -5,11 +5,11 @@ import { CompoundPredicate } from '../Predicate/CompoundPredicate'
|
||||
import { ItemWithTags } from './Search/ItemWithTags'
|
||||
import { itemMatchesQuery, itemPassesFilters } from './Search/SearchUtilities'
|
||||
import { ItemFilter, ReferenceLookupCollection, SearchableDecryptedItem } from './Search/Types'
|
||||
import { FilterDisplayOptions } from './DisplayOptions'
|
||||
import { NotesAndFilesDisplayOptions } from './DisplayOptions'
|
||||
import { SystemViewId } from '../../Syncable/SmartView'
|
||||
|
||||
export function computeUnifiedFilterForDisplayOptions(
|
||||
options: FilterDisplayOptions,
|
||||
options: NotesAndFilesDisplayOptions,
|
||||
collection: ReferenceLookupCollection,
|
||||
additionalFilters: ItemFilter[] = [],
|
||||
): ItemFilter {
|
||||
@@ -21,7 +21,7 @@ export function computeUnifiedFilterForDisplayOptions(
|
||||
}
|
||||
|
||||
export function computeFiltersForDisplayOptions(
|
||||
options: FilterDisplayOptions,
|
||||
options: NotesAndFilesDisplayOptions,
|
||||
collection: ReferenceLookupCollection,
|
||||
): ItemFilter[] {
|
||||
const filters: ItemFilter[] = []
|
||||
|
||||
@@ -2,11 +2,19 @@ import { ContentType } from '@standardnotes/common'
|
||||
import { compareValues } from '@standardnotes/utils'
|
||||
import { isDeletedItem, isEncryptedItem } from '../../Abstract/Item'
|
||||
import { ItemDelta } from '../Index/ItemDelta'
|
||||
import { DisplayControllerOptions } from './DisplayOptions'
|
||||
import { AnyDisplayOptions, DisplayControllerDisplayOptions, GenericDisplayOptions } from './DisplayOptions'
|
||||
import { sortTwoItems } from './SortTwoItems'
|
||||
import { UuidToSortedPositionMap, DisplayItem, ReadonlyItemCollection } from './Types'
|
||||
import { CriteriaValidatorInterface } from './Validator/CriteriaValidatorInterface'
|
||||
import { CollectionCriteriaValidator } from './Validator/CollectionCriteriaValidator'
|
||||
import { CustomFilterCriteriaValidator } from './Validator/CustomFilterCriteriaValidator'
|
||||
import { ExcludeVaultsCriteriaValidator } from './Validator/ExcludeVaultsCriteriaValidator'
|
||||
import { ExclusiveVaultCriteriaValidator } from './Validator/ExclusiveVaultCriteriaValidator'
|
||||
import { HiddenContentCriteriaValidator } from './Validator/HiddenContentCriteriaValidator'
|
||||
import { VaultDisplayOptions } from './VaultDisplayOptions'
|
||||
import { isExclusioanaryOptionsValue } from './VaultDisplayOptionsTypes'
|
||||
|
||||
export class ItemDisplayController<I extends DisplayItem> {
|
||||
export class ItemDisplayController<I extends DisplayItem, O extends AnyDisplayOptions = GenericDisplayOptions> {
|
||||
private sortMap: UuidToSortedPositionMap = {}
|
||||
private sortedItems: I[] = []
|
||||
private needsSort = true
|
||||
@@ -14,7 +22,8 @@ export class ItemDisplayController<I extends DisplayItem> {
|
||||
constructor(
|
||||
private readonly collection: ReadonlyItemCollection,
|
||||
public readonly contentTypes: ContentType[],
|
||||
private options: DisplayControllerOptions,
|
||||
private options: DisplayControllerDisplayOptions & O,
|
||||
private vaultOptions?: VaultDisplayOptions,
|
||||
) {
|
||||
this.filterThenSortElements(this.collection.all(this.contentTypes) as I[])
|
||||
}
|
||||
@@ -23,7 +32,18 @@ export class ItemDisplayController<I extends DisplayItem> {
|
||||
return this.sortedItems
|
||||
}
|
||||
|
||||
setDisplayOptions(displayOptions: Partial<DisplayControllerOptions>): void {
|
||||
public getDisplayOptions(): DisplayControllerDisplayOptions & O {
|
||||
return this.options
|
||||
}
|
||||
|
||||
setVaultDisplayOptions(vaultOptions?: VaultDisplayOptions): void {
|
||||
this.vaultOptions = vaultOptions
|
||||
this.needsSort = true
|
||||
|
||||
this.filterThenSortElements(this.collection.all(this.contentTypes) as I[])
|
||||
}
|
||||
|
||||
setDisplayOptions(displayOptions: Partial<DisplayControllerDisplayOptions & O>): void {
|
||||
this.options = { ...this.options, ...displayOptions }
|
||||
this.needsSort = true
|
||||
|
||||
@@ -37,6 +57,29 @@ export class ItemDisplayController<I extends DisplayItem> {
|
||||
this.filterThenSortElements(items as I[])
|
||||
}
|
||||
|
||||
private passesAllFilters(element: I): boolean {
|
||||
const filters: CriteriaValidatorInterface[] = [new CollectionCriteriaValidator(this.collection, element)]
|
||||
|
||||
if (this.vaultOptions) {
|
||||
const options = this.vaultOptions.getOptions()
|
||||
if (isExclusioanaryOptionsValue(options)) {
|
||||
filters.push(new ExcludeVaultsCriteriaValidator([...options.exclude, ...options.locked], element))
|
||||
} else {
|
||||
filters.push(new ExclusiveVaultCriteriaValidator(options.exclusive, element))
|
||||
}
|
||||
}
|
||||
|
||||
if ('hiddenContentTypes' in this.options && this.options.hiddenContentTypes) {
|
||||
filters.push(new HiddenContentCriteriaValidator(this.options.hiddenContentTypes, element))
|
||||
}
|
||||
|
||||
if ('customFilter' in this.options && this.options.customFilter) {
|
||||
filters.push(new CustomFilterCriteriaValidator(this.options.customFilter, element))
|
||||
}
|
||||
|
||||
return filters.every((f) => f.passes())
|
||||
}
|
||||
|
||||
private filterThenSortElements(elements: I[]): void {
|
||||
for (const element of elements) {
|
||||
const previousIndex = this.sortMap[element.uuid]
|
||||
@@ -61,13 +104,7 @@ export class ItemDisplayController<I extends DisplayItem> {
|
||||
continue
|
||||
}
|
||||
|
||||
const passes = !this.collection.has(element.uuid)
|
||||
? false
|
||||
: this.options.hiddenContentTypes?.includes(element.content_type)
|
||||
? false
|
||||
: this.options.customFilter
|
||||
? this.options.customFilter(element)
|
||||
: true
|
||||
const passes = this.passesAllFilters(element)
|
||||
|
||||
if (passes) {
|
||||
if (previousElement != undefined) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { SNTag } from '../../../Syncable/Tag'
|
||||
import { FilterDisplayOptions } from '../DisplayOptions'
|
||||
import { NotesAndFilesDisplayOptions } from '../DisplayOptions'
|
||||
import { computeFiltersForDisplayOptions } from '../DisplayOptionsToFilters'
|
||||
import { SearchableItem } from './SearchableItem'
|
||||
import { ReferenceLookupCollection, ItemFilter, SearchQuery, SearchableDecryptedItem } from './Types'
|
||||
@@ -13,8 +13,8 @@ enum MatchResult {
|
||||
Uuid = 5,
|
||||
}
|
||||
|
||||
export function itemsMatchingOptions(
|
||||
options: FilterDisplayOptions,
|
||||
export function notesAndFilesMatchingOptions(
|
||||
options: NotesAndFilesDisplayOptions,
|
||||
fromItems: SearchableDecryptedItem[],
|
||||
collection: ReferenceLookupCollection,
|
||||
): SearchableItem[] {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ItemInterface } from '../../../Abstract/Item'
|
||||
import { ReadonlyItemCollection } from '../Types'
|
||||
import { CriteriaValidatorInterface } from './CriteriaValidatorInterface'
|
||||
|
||||
export class CollectionCriteriaValidator implements CriteriaValidatorInterface {
|
||||
constructor(private collection: ReadonlyItemCollection, private element: ItemInterface) {}
|
||||
|
||||
public passes(): boolean {
|
||||
return this.collection.has(this.element.uuid)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface CriteriaValidatorInterface {
|
||||
passes(): boolean
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { DecryptedItemInterface } from '../../../Abstract/Item'
|
||||
import { DisplayControllerCustomFilter } from '../Types'
|
||||
import { CriteriaValidatorInterface } from './CriteriaValidatorInterface'
|
||||
|
||||
export class CustomFilterCriteriaValidator implements CriteriaValidatorInterface {
|
||||
constructor(private customFilter: DisplayControllerCustomFilter, private element: DecryptedItemInterface) {}
|
||||
|
||||
public passes(): boolean {
|
||||
return this.customFilter(this.element)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { CriteriaValidatorInterface } from './CriteriaValidatorInterface'
|
||||
import { DecryptedItemInterface } from '../../../Abstract/Item'
|
||||
import { KeySystemIdentifier } from '../../../Syncable/KeySystemRootKey/KeySystemIdentifier'
|
||||
|
||||
export class ExcludeVaultsCriteriaValidator implements CriteriaValidatorInterface {
|
||||
constructor(private excludeVaults: KeySystemIdentifier[], private element: DecryptedItemInterface) {}
|
||||
|
||||
public passes(): boolean {
|
||||
const doesElementBelongToExcludedVault = this.excludeVaults.some(
|
||||
(vault) => this.element.key_system_identifier === vault,
|
||||
)
|
||||
|
||||
return !doesElementBelongToExcludedVault
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { CriteriaValidatorInterface } from './CriteriaValidatorInterface'
|
||||
import { DecryptedItemInterface } from '../../../Abstract/Item'
|
||||
import { KeySystemIdentifier } from '../../../Syncable/KeySystemRootKey/KeySystemIdentifier'
|
||||
|
||||
export class ExclusiveVaultCriteriaValidator implements CriteriaValidatorInterface {
|
||||
constructor(private exclusiveVault: KeySystemIdentifier, private element: DecryptedItemInterface) {}
|
||||
|
||||
public passes(): boolean {
|
||||
return this.element.key_system_identifier === this.exclusiveVault
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { DecryptedItemInterface } from './../../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { CriteriaValidatorInterface } from './CriteriaValidatorInterface'
|
||||
|
||||
export class HiddenContentCriteriaValidator implements CriteriaValidatorInterface {
|
||||
constructor(private hiddenContentTypes: ContentType[], private element: DecryptedItemInterface) {}
|
||||
|
||||
public passes(): boolean {
|
||||
return !this.hiddenContentTypes.includes(this.element.content_type)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { VaultListingInterface } from '../../Syncable/VaultListing/VaultListingInterface'
|
||||
import { uniqueArray } from '@standardnotes/utils'
|
||||
import {
|
||||
ExclusioanaryOptions,
|
||||
ExclusiveOptions,
|
||||
VaultDisplayOptionsPersistable,
|
||||
isExclusioanaryOptionsValue,
|
||||
} from './VaultDisplayOptionsTypes'
|
||||
import { KeySystemIdentifier } from '../../Syncable/KeySystemRootKey/KeySystemIdentifier'
|
||||
|
||||
function KeySystemIdentifiers(vaults: VaultListingInterface[]): KeySystemIdentifier[] {
|
||||
return vaults.map((vault) => vault.systemIdentifier)
|
||||
}
|
||||
|
||||
export class VaultDisplayOptions {
|
||||
constructor(private readonly options: ExclusioanaryOptions | ExclusiveOptions) {}
|
||||
|
||||
public getOptions(): ExclusioanaryOptions | ExclusiveOptions {
|
||||
return this.options
|
||||
}
|
||||
|
||||
public getExclusivelyShownVault(): KeySystemIdentifier {
|
||||
if (isExclusioanaryOptionsValue(this.options)) {
|
||||
throw new Error('Not in exclusive display mode')
|
||||
}
|
||||
|
||||
return this.options.exclusive
|
||||
}
|
||||
|
||||
public isInExclusiveDisplayMode(): boolean {
|
||||
return !isExclusioanaryOptionsValue(this.options)
|
||||
}
|
||||
|
||||
public isVaultExplicitelyExcluded(vault: VaultListingInterface): boolean {
|
||||
if (isExclusioanaryOptionsValue(this.options)) {
|
||||
return this.options.exclude.some((excludedVault) => excludedVault === vault.systemIdentifier)
|
||||
} else if (this.options.exclusive) {
|
||||
return this.options.exclusive !== vault.systemIdentifier
|
||||
}
|
||||
|
||||
throw new Error('Invalid vault display options')
|
||||
}
|
||||
|
||||
isVaultExclusivelyShown(vault: VaultListingInterface): boolean {
|
||||
return !isExclusioanaryOptionsValue(this.options) && this.options.exclusive === vault.systemIdentifier
|
||||
}
|
||||
|
||||
isVaultDisabledOrLocked(vault: VaultListingInterface): boolean {
|
||||
if (isExclusioanaryOptionsValue(this.options)) {
|
||||
const matchingLocked = this.options.locked.find((lockedVault) => lockedVault === vault.systemIdentifier)
|
||||
if (matchingLocked) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return this.isVaultExplicitelyExcluded(vault)
|
||||
}
|
||||
|
||||
getPersistableValue(): VaultDisplayOptionsPersistable {
|
||||
return this.options
|
||||
}
|
||||
|
||||
newOptionsByIntakingLockedVaults(lockedVaults: VaultListingInterface[]): VaultDisplayOptions {
|
||||
if (isExclusioanaryOptionsValue(this.options)) {
|
||||
return new VaultDisplayOptions({ exclude: this.options.exclude, locked: KeySystemIdentifiers(lockedVaults) })
|
||||
} else {
|
||||
return new VaultDisplayOptions({ exclusive: this.options.exclusive })
|
||||
}
|
||||
}
|
||||
|
||||
newOptionsByExcludingVault(vault: VaultListingInterface, lockedVaults: VaultListingInterface[]): VaultDisplayOptions {
|
||||
return this.newOptionsByExcludingVaults([vault], lockedVaults)
|
||||
}
|
||||
|
||||
newOptionsByExcludingVaults(
|
||||
vaults: VaultListingInterface[],
|
||||
lockedVaults: VaultListingInterface[],
|
||||
): VaultDisplayOptions {
|
||||
if (isExclusioanaryOptionsValue(this.options)) {
|
||||
return new VaultDisplayOptions({
|
||||
exclude: uniqueArray([...this.options.exclude, ...KeySystemIdentifiers(vaults)]),
|
||||
locked: KeySystemIdentifiers(lockedVaults),
|
||||
})
|
||||
} else {
|
||||
return new VaultDisplayOptions({
|
||||
exclude: KeySystemIdentifiers(vaults),
|
||||
locked: KeySystemIdentifiers(lockedVaults),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
newOptionsByUnexcludingVault(
|
||||
vault: VaultListingInterface,
|
||||
lockedVaults: VaultListingInterface[],
|
||||
): VaultDisplayOptions {
|
||||
if (isExclusioanaryOptionsValue(this.options)) {
|
||||
return new VaultDisplayOptions({
|
||||
exclude: this.options.exclude.filter((excludedVault) => excludedVault !== vault.systemIdentifier),
|
||||
locked: KeySystemIdentifiers(lockedVaults),
|
||||
})
|
||||
} else {
|
||||
return new VaultDisplayOptions({ exclude: [], locked: KeySystemIdentifiers(lockedVaults) })
|
||||
}
|
||||
}
|
||||
|
||||
static FromPersistableValue(value: VaultDisplayOptionsPersistable): VaultDisplayOptions {
|
||||
return new VaultDisplayOptions(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { KeySystemIdentifier } from '../../Syncable/KeySystemRootKey/KeySystemIdentifier'
|
||||
|
||||
export type ExclusioanaryOptions = { exclude: KeySystemIdentifier[]; locked: KeySystemIdentifier[] }
|
||||
export type ExclusiveOptions = { exclusive: KeySystemIdentifier }
|
||||
|
||||
export function isExclusioanaryOptionsValue(
|
||||
options: ExclusioanaryOptions | ExclusiveOptions,
|
||||
): options is ExclusioanaryOptions {
|
||||
return 'exclude' in options || 'locked' in options
|
||||
}
|
||||
|
||||
export type VaultDisplayOptionsPersistable = ExclusioanaryOptions | ExclusiveOptions
|
||||
@@ -6,3 +6,5 @@ export * from './Search/SearchableItem'
|
||||
export * from './Search/SearchUtilities'
|
||||
export * from './Search/Types'
|
||||
export * from './Types'
|
||||
export * from './VaultDisplayOptions'
|
||||
export * from './VaultDisplayOptionsTypes'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
|
||||
export function ContentTypeUsesKeySystemRootKeyEncryption(contentType: ContentType): boolean {
|
||||
return contentType === ContentType.KeySystemItemsKey
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ContentTypesUsingRootKeyEncryption } from './ContentTypesUsingRootKeyEncryption'
|
||||
|
||||
export function ContentTypeUsesRootKeyEncryption(contentType: ContentType): boolean {
|
||||
return ContentTypesUsingRootKeyEncryption().includes(contentType)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
|
||||
export function ContentTypesUsingRootKeyEncryption(): ContentType[] {
|
||||
return [
|
||||
ContentType.RootKey,
|
||||
ContentType.ItemsKey,
|
||||
ContentType.EncryptedStorage,
|
||||
ContentType.TrustedContact,
|
||||
ContentType.KeySystemRootKey,
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export type PersistentSignatureData =
|
||||
| {
|
||||
required: true
|
||||
contentHash: string
|
||||
result: {
|
||||
passes: boolean
|
||||
publicKey: string
|
||||
signature: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
required: false
|
||||
contentHash: string
|
||||
result?: {
|
||||
passes: boolean
|
||||
publicKey: string
|
||||
signature: string
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { ItemContent, SpecializedContent } from '../../Abstract/Content/ItemContent'
|
||||
|
||||
export interface KeySystemItemsKeyContentSpecialized extends SpecializedContent {
|
||||
version: ProtocolVersion
|
||||
creationTimestamp: number
|
||||
itemsKey: string
|
||||
rootKeyToken: string
|
||||
}
|
||||
|
||||
export type KeySystemItemsKeyContent = KeySystemItemsKeyContentSpecialized & ItemContent
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { KeySystemItemsKeyContent } from './KeySystemItemsKeyContent'
|
||||
|
||||
export interface KeySystemItemsKeyInterface extends DecryptedItemInterface<KeySystemItemsKeyContent> {
|
||||
readonly creationTimestamp: number
|
||||
readonly rootKeyToken: string
|
||||
|
||||
get keyVersion(): ProtocolVersion
|
||||
get itemsKey(): string
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface KeySystemItemsKeyMutatorInterface extends DecryptedItemMutator {}
|
||||
@@ -0,0 +1 @@
|
||||
export type KeySystemIdentifier = string
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ContentType, ProtocolVersion } from '@standardnotes/common'
|
||||
import { ConflictStrategy, DecryptedItem } from '../../Abstract/Item'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload'
|
||||
import { HistoryEntryInterface } from '../../Runtime/History'
|
||||
import { KeySystemRootKeyContent } from './KeySystemRootKeyContent'
|
||||
import { KeySystemRootKeyInterface } from './KeySystemRootKeyInterface'
|
||||
import { KeySystemIdentifier } from './KeySystemIdentifier'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
|
||||
export function isKeySystemRootKey(x: { content_type: ContentType }): x is KeySystemRootKey {
|
||||
return x.content_type === ContentType.KeySystemRootKey
|
||||
}
|
||||
|
||||
export class KeySystemRootKey extends DecryptedItem<KeySystemRootKeyContent> implements KeySystemRootKeyInterface {
|
||||
keyParams: KeySystemRootKeyParamsInterface
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
|
||||
key: string
|
||||
keyVersion: ProtocolVersion
|
||||
token: string
|
||||
|
||||
constructor(payload: DecryptedPayloadInterface<KeySystemRootKeyContent>) {
|
||||
super(payload)
|
||||
|
||||
this.keyParams = payload.content.keyParams
|
||||
this.systemIdentifier = payload.content.systemIdentifier
|
||||
|
||||
this.key = payload.content.key
|
||||
this.keyVersion = payload.content.keyVersion
|
||||
this.token = payload.content.token
|
||||
}
|
||||
|
||||
override strategyWhenConflictingWithItem(
|
||||
item: KeySystemRootKey,
|
||||
_previousRevision?: HistoryEntryInterface,
|
||||
): ConflictStrategy {
|
||||
const baseKeyTimestamp = this.keyParams.creationTimestamp
|
||||
const incomingKeyTimestamp = item.keyParams.creationTimestamp
|
||||
|
||||
return incomingKeyTimestamp > baseKeyTimestamp ? ConflictStrategy.KeepApply : ConflictStrategy.KeepBase
|
||||
}
|
||||
|
||||
get itemsKey(): string {
|
||||
return this.key
|
||||
}
|
||||
|
||||
override get key_system_identifier(): undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
override get shared_vault_uuid(): undefined {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { KeySystemIdentifier } from './KeySystemIdentifier'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
|
||||
export type KeySystemRootKeyContentSpecialized = {
|
||||
keyParams: KeySystemRootKeyParamsInterface
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
|
||||
key: string
|
||||
keyVersion: ProtocolVersion
|
||||
|
||||
token: string
|
||||
}
|
||||
|
||||
export type KeySystemRootKeyContent = KeySystemRootKeyContentSpecialized & ItemContent
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { KeySystemRootKeyContent } from './KeySystemRootKeyContent'
|
||||
import { KeySystemIdentifier } from './KeySystemIdentifier'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
|
||||
export interface KeySystemRootKeyInterface extends DecryptedItemInterface<KeySystemRootKeyContent> {
|
||||
keyParams: KeySystemRootKeyParamsInterface
|
||||
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
|
||||
key: string
|
||||
keyVersion: ProtocolVersion
|
||||
|
||||
/**
|
||||
* A token is passed to all items keys created while this root key was active.
|
||||
* When determining which items key a client should use to encrypt new items or new changes,
|
||||
* it should look for items keys which have the current root key token. This prevents
|
||||
* the server from dictating which items key a client should use, and also prevents a server from withholding
|
||||
* items keys from sync results, which would otherwise compel a client to choose between its available items keys,
|
||||
* which may be old or rotated.
|
||||
*
|
||||
* This token is part of the encrypted payload of both the root key and corresponding items keys. While not
|
||||
* necessarily destructive if leaked, it prevents a malicious server from creating a compromised items key for a vault.
|
||||
*/
|
||||
token: string
|
||||
|
||||
get itemsKey(): string
|
||||
|
||||
/**
|
||||
* Key system root keys pertain to a key system, but they are not actually encrypted inside a key system, but rather
|
||||
* saved as a normal item in the user's account. An item's key_system_identifier tells the cryptographic system which
|
||||
* keys to use to encrypt, but a key system rootkey's systemIdentifier is just a reference to that identifier that doesn't
|
||||
* bind the item to a specific vault system's cryptographic keys.
|
||||
*/
|
||||
get key_system_identifier(): undefined
|
||||
get shared_vault_uuid(): undefined
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item'
|
||||
import { KeySystemRootKeyContent } from './KeySystemRootKeyContent'
|
||||
|
||||
export class KeySystemRootKeyMutator extends DecryptedItemMutator<KeySystemRootKeyContent> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum KeySystemRootKeyStorageMode {
|
||||
Synced = 'synced',
|
||||
Local = 'local',
|
||||
Ephemeral = 'ephemeral',
|
||||
}
|
||||
@@ -9,10 +9,10 @@ import { FillItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { Predicate } from '../../Runtime/Predicate/Predicate'
|
||||
import { CompoundPredicate } from '../../Runtime/Predicate/CompoundPredicate'
|
||||
import { PayloadTimestampDefaults } from '../../Abstract/Payload'
|
||||
import { FilterDisplayOptions } from '../../Runtime/Display'
|
||||
import { NotesAndFilesDisplayOptions } from '../../Runtime/Display'
|
||||
import { FileItem } from '../File'
|
||||
|
||||
export function BuildSmartViews(options: FilterDisplayOptions): SmartView[] {
|
||||
export function BuildSmartViews(options: NotesAndFilesDisplayOptions): SmartView[] {
|
||||
const notes = new SmartView(
|
||||
new DecryptedPayload({
|
||||
uuid: SystemViewId.AllNotes,
|
||||
@@ -100,7 +100,7 @@ export function BuildSmartViews(options: FilterDisplayOptions): SmartView[] {
|
||||
return [notes, files, starred, archived, trash, untagged, conflicts]
|
||||
}
|
||||
|
||||
function allNotesPredicate(options: FilterDisplayOptions) {
|
||||
function allNotesPredicate(options: NotesAndFilesDisplayOptions) {
|
||||
const subPredicates: Predicate<SNNote>[] = [new Predicate('content_type', '=', ContentType.Note)]
|
||||
|
||||
if (options.includeTrashed === false) {
|
||||
@@ -120,7 +120,7 @@ function allNotesPredicate(options: FilterDisplayOptions) {
|
||||
return predicate
|
||||
}
|
||||
|
||||
function filesPredicate(options: FilterDisplayOptions) {
|
||||
function filesPredicate(options: NotesAndFilesDisplayOptions) {
|
||||
const subPredicates: Predicate<FileItem>[] = [new Predicate('content_type', '=', ContentType.File)]
|
||||
|
||||
if (options.includeTrashed === false) {
|
||||
@@ -140,7 +140,7 @@ function filesPredicate(options: FilterDisplayOptions) {
|
||||
return predicate
|
||||
}
|
||||
|
||||
function archivedNotesPredicate(options: FilterDisplayOptions) {
|
||||
function archivedNotesPredicate(options: NotesAndFilesDisplayOptions) {
|
||||
const subPredicates: Predicate<SNNote>[] = [
|
||||
new Predicate('archived', '=', true),
|
||||
new Predicate('content_type', '=', ContentType.Note),
|
||||
@@ -159,7 +159,7 @@ function archivedNotesPredicate(options: FilterDisplayOptions) {
|
||||
return predicate
|
||||
}
|
||||
|
||||
function trashedNotesPredicate(options: FilterDisplayOptions) {
|
||||
function trashedNotesPredicate(options: NotesAndFilesDisplayOptions) {
|
||||
const subPredicates: Predicate<SNNote>[] = [
|
||||
new Predicate('trashed', '=', true),
|
||||
new Predicate('content_type', '=', ContentType.Note),
|
||||
@@ -178,7 +178,7 @@ function trashedNotesPredicate(options: FilterDisplayOptions) {
|
||||
return predicate
|
||||
}
|
||||
|
||||
function untaggedNotesPredicate(options: FilterDisplayOptions) {
|
||||
function untaggedNotesPredicate(options: NotesAndFilesDisplayOptions) {
|
||||
const subPredicates = [
|
||||
new Predicate('content_type', '=', ContentType.Note),
|
||||
new Predicate<ItemWithTags>('tagsCount', '=', 0),
|
||||
@@ -197,7 +197,7 @@ function untaggedNotesPredicate(options: FilterDisplayOptions) {
|
||||
return predicate
|
||||
}
|
||||
|
||||
function starredNotesPredicate(options: FilterDisplayOptions) {
|
||||
function starredNotesPredicate(options: NotesAndFilesDisplayOptions) {
|
||||
const subPredicates: Predicate<SNNote>[] = [
|
||||
new Predicate('starred', '=', true),
|
||||
new Predicate('content_type', '=', ContentType.Note),
|
||||
@@ -216,7 +216,7 @@ function starredNotesPredicate(options: FilterDisplayOptions) {
|
||||
return predicate
|
||||
}
|
||||
|
||||
function conflictsPredicate(options: FilterDisplayOptions) {
|
||||
function conflictsPredicate(options: NotesAndFilesDisplayOptions) {
|
||||
const subPredicates: Predicate<SNNote>[] = [new Predicate('content_type', '=', ContentType.Note)]
|
||||
|
||||
if (options.includeTrashed === false) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { ContactPublicKeySetInterface } from './ContactPublicKeySetInterface'
|
||||
import { ContactPublicKeySetJsonInterface } from './ContactPublicKeySetJsonInterface'
|
||||
|
||||
export class ContactPublicKeySet implements ContactPublicKeySetInterface {
|
||||
encryption: string
|
||||
signing: string
|
||||
timestamp: Date
|
||||
isRevoked: boolean
|
||||
previousKeySet?: ContactPublicKeySet
|
||||
|
||||
constructor(
|
||||
encryption: string,
|
||||
signing: string,
|
||||
timestamp: Date,
|
||||
isRevoked: boolean,
|
||||
previousKeySet: ContactPublicKeySet | undefined,
|
||||
) {
|
||||
this.encryption = encryption
|
||||
this.signing = signing
|
||||
this.timestamp = timestamp
|
||||
this.isRevoked = isRevoked
|
||||
this.previousKeySet = previousKeySet
|
||||
}
|
||||
|
||||
public findKeySet(params: {
|
||||
targetEncryptionPublicKey: string
|
||||
targetSigningPublicKey: string
|
||||
}): ContactPublicKeySetInterface | undefined {
|
||||
if (this.encryption === params.targetEncryptionPublicKey && this.signing === params.targetSigningPublicKey) {
|
||||
return this
|
||||
}
|
||||
|
||||
if (this.previousKeySet) {
|
||||
return this.previousKeySet.findKeySet(params)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
public findKeySetWithSigningKey(signingKey: string): ContactPublicKeySetInterface | undefined {
|
||||
if (this.signing === signingKey) {
|
||||
return this
|
||||
}
|
||||
|
||||
if (this.previousKeySet) {
|
||||
return this.previousKeySet.findKeySetWithSigningKey(signingKey)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
findKeySetWithPublicKey(publicKey: string): ContactPublicKeySetInterface | undefined {
|
||||
if (this.encryption === publicKey) {
|
||||
return this
|
||||
}
|
||||
|
||||
if (this.previousKeySet) {
|
||||
return this.previousKeySet.findKeySetWithPublicKey(publicKey)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
static FromJson(json: ContactPublicKeySetJsonInterface): ContactPublicKeySetInterface {
|
||||
return new ContactPublicKeySet(
|
||||
json.encryption,
|
||||
json.signing,
|
||||
new Date(json.timestamp),
|
||||
json.isRevoked,
|
||||
json.previousKeySet ? ContactPublicKeySet.FromJson(json.previousKeySet) : undefined,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface ContactPublicKeySetInterface {
|
||||
encryption: string
|
||||
signing: string
|
||||
timestamp: Date
|
||||
isRevoked: boolean
|
||||
previousKeySet?: ContactPublicKeySetInterface
|
||||
|
||||
findKeySet(params: {
|
||||
targetEncryptionPublicKey: string
|
||||
targetSigningPublicKey: string
|
||||
}): ContactPublicKeySetInterface | undefined
|
||||
|
||||
findKeySetWithPublicKey(publicKey: string): ContactPublicKeySetInterface | undefined
|
||||
findKeySetWithSigningKey(signingKey: string): ContactPublicKeySetInterface | undefined
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface ContactPublicKeySetJsonInterface {
|
||||
encryption: string
|
||||
signing: string
|
||||
timestamp: Date
|
||||
isRevoked: boolean
|
||||
previousKeySet?: ContactPublicKeySetJsonInterface
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ContactPublicKeySetInterface } from './ContactPublicKeySetInterface'
|
||||
|
||||
export type FindPublicKeySetResult =
|
||||
| {
|
||||
publicKeySet: ContactPublicKeySetInterface
|
||||
current: boolean
|
||||
}
|
||||
| undefined
|
||||
@@ -0,0 +1,77 @@
|
||||
import { ConflictStrategy, DecryptedItem, DecryptedItemInterface } from '../../Abstract/Item'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload'
|
||||
import { HistoryEntryInterface } from '../../Runtime/History'
|
||||
import { TrustedContactContent } from './TrustedContactContent'
|
||||
import { TrustedContactInterface } from './TrustedContactInterface'
|
||||
import { FindPublicKeySetResult } from './PublicKeySet/FindPublicKeySetResult'
|
||||
import { ContactPublicKeySet } from './PublicKeySet/ContactPublicKeySet'
|
||||
import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface'
|
||||
import { Predicate } from '../../Runtime/Predicate/Predicate'
|
||||
|
||||
export class TrustedContact extends DecryptedItem<TrustedContactContent> implements TrustedContactInterface {
|
||||
static singletonPredicate = new Predicate<TrustedContact>('isMe', '=', true)
|
||||
|
||||
name: string
|
||||
contactUuid: string
|
||||
publicKeySet: ContactPublicKeySetInterface
|
||||
isMe: boolean
|
||||
|
||||
constructor(payload: DecryptedPayloadInterface<TrustedContactContent>) {
|
||||
super(payload)
|
||||
|
||||
this.name = payload.content.name
|
||||
this.contactUuid = payload.content.contactUuid
|
||||
this.publicKeySet = ContactPublicKeySet.FromJson(payload.content.publicKeySet)
|
||||
this.isMe = payload.content.isMe
|
||||
}
|
||||
|
||||
override get isSingleton(): true {
|
||||
return true
|
||||
}
|
||||
|
||||
override singletonPredicate(): Predicate<TrustedContact> {
|
||||
return TrustedContact.singletonPredicate
|
||||
}
|
||||
|
||||
public findKeySet(params: {
|
||||
targetEncryptionPublicKey: string
|
||||
targetSigningPublicKey: string
|
||||
}): FindPublicKeySetResult {
|
||||
const set = this.publicKeySet.findKeySet(params)
|
||||
if (!set) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
publicKeySet: set,
|
||||
current: set === this.publicKeySet,
|
||||
}
|
||||
}
|
||||
|
||||
isPublicKeyTrusted(encryptionPublicKey: string): boolean {
|
||||
const keySet = this.publicKeySet.findKeySetWithPublicKey(encryptionPublicKey)
|
||||
|
||||
if (keySet && !keySet.isRevoked) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
isSigningKeyTrusted(signingKey: string): boolean {
|
||||
const keySet = this.publicKeySet.findKeySetWithSigningKey(signingKey)
|
||||
|
||||
if (keySet && !keySet.isRevoked) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override strategyWhenConflictingWithItem(
|
||||
_item: DecryptedItemInterface,
|
||||
_previousRevision?: HistoryEntryInterface,
|
||||
): ConflictStrategy {
|
||||
return ConflictStrategy.KeepBase
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface'
|
||||
|
||||
export type TrustedContactContentSpecialized = {
|
||||
name: string
|
||||
contactUuid: string
|
||||
publicKeySet: ContactPublicKeySetInterface
|
||||
isMe: boolean
|
||||
}
|
||||
|
||||
export type TrustedContactContent = TrustedContactContentSpecialized & ItemContent
|
||||
@@ -0,0 +1,16 @@
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { FindPublicKeySetResult } from './PublicKeySet/FindPublicKeySetResult'
|
||||
import { TrustedContactContent } from './TrustedContactContent'
|
||||
import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface'
|
||||
|
||||
export interface TrustedContactInterface extends DecryptedItemInterface<TrustedContactContent> {
|
||||
name: string
|
||||
contactUuid: string
|
||||
publicKeySet: ContactPublicKeySetInterface
|
||||
isMe: boolean
|
||||
|
||||
findKeySet(params: { targetEncryptionPublicKey: string; targetSigningPublicKey: string }): FindPublicKeySetResult
|
||||
|
||||
isPublicKeyTrusted(encryptionPublicKey: string): boolean
|
||||
isSigningKeyTrusted(signingKey: string): boolean
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item'
|
||||
import { TrustedContactContent } from './TrustedContactContent'
|
||||
import { TrustedContactInterface } from './TrustedContactInterface'
|
||||
import { ContactPublicKeySet } from './PublicKeySet/ContactPublicKeySet'
|
||||
|
||||
export class TrustedContactMutator extends DecryptedItemMutator<TrustedContactContent, TrustedContactInterface> {
|
||||
set name(newName: string) {
|
||||
this.mutableContent.name = newName
|
||||
}
|
||||
|
||||
addPublicKey(params: { encryption: string; signing: string }): void {
|
||||
const newKey = new ContactPublicKeySet(
|
||||
params.encryption,
|
||||
params.signing,
|
||||
new Date(),
|
||||
false,
|
||||
this.immutableItem.publicKeySet,
|
||||
)
|
||||
|
||||
this.mutableContent.publicKeySet = newKey
|
||||
}
|
||||
|
||||
replacePublicKeySet(publicKeySet: ContactPublicKeySet): void {
|
||||
this.mutableContent.publicKeySet = publicKeySet
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { ConflictStrategy, DecryptedItem } from '../../Abstract/Item'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload'
|
||||
import { HistoryEntryInterface } from '../../Runtime/History'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
import { KeySystemRootKeyPasswordType } from '../../Local/KeyParams/KeySystemRootKeyPasswordType'
|
||||
import { SharedVaultListingInterface, VaultListingInterface } from './VaultListingInterface'
|
||||
import { VaultListingContent } from './VaultListingContent'
|
||||
import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode'
|
||||
import { VaultListingSharingInfo } from './VaultListingSharingInfo'
|
||||
import { KeySystemIdentifier } from '../KeySystemRootKey/KeySystemIdentifier'
|
||||
|
||||
export class VaultListing extends DecryptedItem<VaultListingContent> implements VaultListingInterface {
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
|
||||
rootKeyParams: KeySystemRootKeyParamsInterface
|
||||
keyStorageMode: KeySystemRootKeyStorageMode
|
||||
|
||||
name: string
|
||||
description?: string
|
||||
|
||||
sharing?: VaultListingSharingInfo
|
||||
|
||||
constructor(payload: DecryptedPayloadInterface<VaultListingContent>) {
|
||||
super(payload)
|
||||
|
||||
this.systemIdentifier = payload.content.systemIdentifier
|
||||
|
||||
this.rootKeyParams = payload.content.rootKeyParams
|
||||
this.keyStorageMode = payload.content.keyStorageMode
|
||||
|
||||
this.name = payload.content.name
|
||||
this.description = payload.content.description
|
||||
|
||||
this.sharing = payload.content.sharing
|
||||
}
|
||||
|
||||
override strategyWhenConflictingWithItem(
|
||||
item: VaultListing,
|
||||
_previousRevision?: HistoryEntryInterface,
|
||||
): ConflictStrategy {
|
||||
const baseKeyTimestamp = this.rootKeyParams.creationTimestamp
|
||||
const incomingKeyTimestamp = item.rootKeyParams.creationTimestamp
|
||||
|
||||
return incomingKeyTimestamp > baseKeyTimestamp ? ConflictStrategy.KeepApply : ConflictStrategy.KeepBase
|
||||
}
|
||||
|
||||
get keyPasswordType(): KeySystemRootKeyPasswordType {
|
||||
return this.rootKeyParams.passwordType
|
||||
}
|
||||
|
||||
isSharedVaultListing(): this is SharedVaultListingInterface {
|
||||
return this.sharing != undefined
|
||||
}
|
||||
|
||||
override get key_system_identifier(): undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
override get shared_vault_uuid(): undefined {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ItemContent, SpecializedContent } from '../../Abstract/Content/ItemContent'
|
||||
import { KeySystemIdentifier } from '../KeySystemRootKey/KeySystemIdentifier'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode'
|
||||
import { VaultListingSharingInfo } from './VaultListingSharingInfo'
|
||||
|
||||
export interface VaultListingContentSpecialized extends SpecializedContent {
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
|
||||
rootKeyParams: KeySystemRootKeyParamsInterface
|
||||
keyStorageMode: KeySystemRootKeyStorageMode
|
||||
|
||||
name: string
|
||||
description?: string
|
||||
|
||||
sharing?: VaultListingSharingInfo
|
||||
}
|
||||
|
||||
export type VaultListingContent = VaultListingContentSpecialized & ItemContent
|
||||
@@ -0,0 +1,29 @@
|
||||
import { KeySystemIdentifier } from '../KeySystemRootKey/KeySystemIdentifier'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
import { KeySystemRootKeyPasswordType } from '../../Local/KeyParams/KeySystemRootKeyPasswordType'
|
||||
import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode'
|
||||
import { VaultListingSharingInfo } from './VaultListingSharingInfo'
|
||||
import { VaultListingContent } from './VaultListingContent'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item'
|
||||
|
||||
export interface VaultListingInterface extends DecryptedItemInterface<VaultListingContent> {
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
|
||||
rootKeyParams: KeySystemRootKeyParamsInterface
|
||||
keyStorageMode: KeySystemRootKeyStorageMode
|
||||
|
||||
name: string
|
||||
description?: string
|
||||
|
||||
sharing?: VaultListingSharingInfo
|
||||
|
||||
get keyPasswordType(): KeySystemRootKeyPasswordType
|
||||
isSharedVaultListing(): this is SharedVaultListingInterface
|
||||
|
||||
get key_system_identifier(): undefined
|
||||
get shared_vault_uuid(): undefined
|
||||
}
|
||||
|
||||
export interface SharedVaultListingInterface extends VaultListingInterface {
|
||||
sharing: VaultListingSharingInfo
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode'
|
||||
import { VaultListingContent } from './VaultListingContent'
|
||||
import { VaultListingSharingInfo } from './VaultListingSharingInfo'
|
||||
|
||||
export class VaultListingMutator extends DecryptedItemMutator<VaultListingContent> {
|
||||
set name(name: string) {
|
||||
this.mutableContent.name = name
|
||||
}
|
||||
|
||||
set description(description: string | undefined) {
|
||||
this.mutableContent.description = description
|
||||
}
|
||||
|
||||
set sharing(sharing: VaultListingSharingInfo | undefined) {
|
||||
this.mutableContent.sharing = sharing
|
||||
}
|
||||
|
||||
set rootKeyParams(rootKeyParams: KeySystemRootKeyParamsInterface) {
|
||||
this.mutableContent.rootKeyParams = rootKeyParams
|
||||
}
|
||||
|
||||
set keyStorageMode(keyStorageMode: KeySystemRootKeyStorageMode) {
|
||||
this.mutableContent.keyStorageMode = keyStorageMode
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export type VaultListingSharingInfo = {
|
||||
sharedVaultUuid: string
|
||||
ownerUserUuid: string
|
||||
}
|
||||
@@ -23,6 +23,16 @@ import { NoteMutator } from '../../Syncable/Note/NoteMutator'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
import { DeletedItem } from '../../Abstract/Item/Implementations/DeletedItem'
|
||||
import { EncryptedItemInterface } from '../../Abstract/Item/Interfaces/EncryptedItem'
|
||||
import { DeletedItemInterface } from '../../Abstract/Item/Interfaces/DeletedItem'
|
||||
import { SmartViewMutator } from '../../Syncable/SmartView'
|
||||
import { TrustedContact } from '../../Syncable/TrustedContact/TrustedContact'
|
||||
import { TrustedContactMutator } from '../../Syncable/TrustedContact/TrustedContactMutator'
|
||||
import { KeySystemRootKey } from '../../Syncable/KeySystemRootKey/KeySystemRootKey'
|
||||
import { KeySystemRootKeyMutator } from '../../Syncable/KeySystemRootKey/KeySystemRootKeyMutator'
|
||||
import { VaultListing } from '../../Syncable/VaultListing/VaultListing'
|
||||
import { VaultListingMutator } from '../../Syncable/VaultListing/VaultListingMutator'
|
||||
import {
|
||||
DeletedPayloadInterface,
|
||||
EncryptedPayloadInterface,
|
||||
@@ -30,10 +40,6 @@ import {
|
||||
isDeletedPayload,
|
||||
isEncryptedPayload,
|
||||
} from '../../Abstract/Payload'
|
||||
import { DeletedItem } from '../../Abstract/Item/Implementations/DeletedItem'
|
||||
import { EncryptedItemInterface } from '../../Abstract/Item/Interfaces/EncryptedItem'
|
||||
import { DeletedItemInterface } from '../../Abstract/Item/Interfaces/DeletedItem'
|
||||
import { SmartViewMutator } from '../../Syncable/SmartView'
|
||||
|
||||
type ItemClass<C extends ItemContent = ItemContent> = new (payload: DecryptedPayloadInterface<C>) => DecryptedItem<C>
|
||||
|
||||
@@ -53,6 +59,9 @@ const ContentTypeClassMapping: Partial<Record<ContentType, MappingEntry>> = {
|
||||
mutatorClass: ActionsExtensionMutator,
|
||||
},
|
||||
[ContentType.Component]: { itemClass: SNComponent, mutatorClass: ComponentMutator },
|
||||
[ContentType.KeySystemRootKey]: { itemClass: KeySystemRootKey, mutatorClass: KeySystemRootKeyMutator },
|
||||
[ContentType.TrustedContact]: { itemClass: TrustedContact, mutatorClass: TrustedContactMutator },
|
||||
[ContentType.VaultListing]: { itemClass: VaultListing, mutatorClass: VaultListingMutator },
|
||||
[ContentType.Editor]: { itemClass: SNEditor },
|
||||
[ContentType.ExtensionRepo]: { itemClass: SNFeatureRepo },
|
||||
[ContentType.File]: { itemClass: FileItem, mutatorClass: FileMutator },
|
||||
@@ -65,13 +74,13 @@ const ContentTypeClassMapping: Partial<Record<ContentType, MappingEntry>> = {
|
||||
|
||||
export function CreateDecryptedMutatorForItem<
|
||||
I extends DecryptedItemInterface,
|
||||
M extends DecryptedItemMutator = DecryptedItemMutator,
|
||||
M extends DecryptedItemMutator<ItemContent, I> = DecryptedItemMutator<ItemContent, I>,
|
||||
>(item: I, type: MutationType): M {
|
||||
const lookupValue = ContentTypeClassMapping[item.content_type]?.mutatorClass
|
||||
if (lookupValue) {
|
||||
return new lookupValue(item, type) as M
|
||||
} else {
|
||||
return new DecryptedItemMutator(item, type) as M
|
||||
return new DecryptedItemMutator<ItemContent, I>(item, type) as M
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ export const mockUuid = () => {
|
||||
return `${currentId++}`
|
||||
}
|
||||
|
||||
export const createNote = (payload?: Partial<NoteContent>): SNNote => {
|
||||
export const createNote = (content?: Partial<NoteContent>): SNNote => {
|
||||
return new SNNote(
|
||||
new DecryptedPayload(
|
||||
{
|
||||
uuid: mockUuid(),
|
||||
content_type: ContentType.Note,
|
||||
content: FillItemContent({ ...payload }),
|
||||
content: FillItemContent({ ...content }),
|
||||
...PayloadTimestampDefaults(),
|
||||
},
|
||||
PayloadSource.Constructor,
|
||||
|
||||
@@ -15,6 +15,7 @@ export * from './Abstract/Contextual/ComponentCreate'
|
||||
export * from './Abstract/Contextual/ComponentRetrieved'
|
||||
export * from './Abstract/Contextual/ContextPayload'
|
||||
export * from './Abstract/Contextual/FilteredServerItem'
|
||||
export * from './Abstract/Contextual/TrustedConflictParams'
|
||||
export * from './Abstract/Contextual/Functions'
|
||||
export * from './Abstract/Contextual/LocalStorage'
|
||||
export * from './Abstract/Contextual/OfflineSyncPush'
|
||||
@@ -25,19 +26,26 @@ export * from './Abstract/Contextual/SessionHistory'
|
||||
export * from './Abstract/Item'
|
||||
export * from './Abstract/Payload'
|
||||
export * from './Abstract/TransferPayload'
|
||||
|
||||
export * from './Api/Subscription/Invitation'
|
||||
export * from './Api/Subscription/InvitationStatus'
|
||||
export * from './Api/Subscription/InviteeIdentifierType'
|
||||
export * from './Api/Subscription/InviterIdentifierType'
|
||||
|
||||
export * from './Device/Environment'
|
||||
export * from './Device/Platform'
|
||||
|
||||
export * from './Local/KeyParams/RootKeyParamsInterface'
|
||||
export * from './Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
export * from './Local/KeyParams/KeySystemRootKeyPasswordType'
|
||||
export * from './Local/RootKey/KeychainTypes'
|
||||
export * from './Local/RootKey/RootKeyContent'
|
||||
export * from './Local/RootKey/RootKeyInterface'
|
||||
export * from './Local/RootKey/RootKeyWithKeyPairsInterface'
|
||||
|
||||
export * from './Runtime/Collection/CollectionSort'
|
||||
export * from './Runtime/Collection/Item/ItemCollection'
|
||||
export * from './Runtime/Collection/Item/TagItemsIndex'
|
||||
export * from './Runtime/Collection/Item/ItemCounter'
|
||||
export * from './Runtime/Collection/Payload/ImmutablePayloadCollection'
|
||||
export * from './Runtime/Collection/Payload/PayloadCollection'
|
||||
export * from './Runtime/Deltas'
|
||||
@@ -57,6 +65,20 @@ export * from './Runtime/Predicate/NotPredicate'
|
||||
export * from './Runtime/Predicate/Operator'
|
||||
export * from './Runtime/Predicate/Predicate'
|
||||
export * from './Runtime/Predicate/Utils'
|
||||
|
||||
export * from './Runtime/AsymmetricMessage/AsymmetricMessagePayload'
|
||||
export * from './Runtime/AsymmetricMessage/AsymmetricMessagePayloadType'
|
||||
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSenderKeypairChanged'
|
||||
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultInvite'
|
||||
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultMetadataChanged'
|
||||
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultRootKeyChanged'
|
||||
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageTrustedContactShare'
|
||||
|
||||
export * from './Runtime/Encryption/PersistentSignatureData'
|
||||
export * from './Runtime/Encryption/ContentTypeUsesRootKeyEncryption'
|
||||
export * from './Runtime/Encryption/ContentTypesUsingRootKeyEncryption'
|
||||
export * from './Runtime/Encryption/ContentTypeUsesKeySystemRootKeyEncryption'
|
||||
|
||||
export * from './Syncable/ActionsExtension'
|
||||
export * from './Syncable/Component'
|
||||
export * from './Syncable/Editor'
|
||||
@@ -69,6 +91,30 @@ export * from './Syncable/SmartView'
|
||||
export * from './Syncable/Tag'
|
||||
export * from './Syncable/Theme'
|
||||
export * from './Syncable/UserPrefs'
|
||||
|
||||
export * from './Syncable/TrustedContact/TrustedContact'
|
||||
export * from './Syncable/TrustedContact/TrustedContactMutator'
|
||||
export * from './Syncable/TrustedContact/TrustedContactContent'
|
||||
export * from './Syncable/TrustedContact/TrustedContactInterface'
|
||||
export * from './Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetInterface'
|
||||
export * from './Syncable/TrustedContact/PublicKeySet/ContactPublicKeySet'
|
||||
|
||||
export * from './Syncable/KeySystemRootKey/KeySystemRootKey'
|
||||
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyMutator'
|
||||
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyContent'
|
||||
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyInterface'
|
||||
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyStorageMode'
|
||||
|
||||
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyInterface'
|
||||
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyContent'
|
||||
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyMutatorInterface'
|
||||
|
||||
export * from './Syncable/VaultListing/VaultListing'
|
||||
export * from './Syncable/VaultListing/VaultListingContent'
|
||||
export * from './Syncable/VaultListing/VaultListingInterface'
|
||||
export * from './Syncable/VaultListing/VaultListingMutator'
|
||||
export * from './Syncable/VaultListing/VaultListingSharingInfo'
|
||||
|
||||
export * from './Utilities/Icon/IconType'
|
||||
export * from './Utilities/Item/FindItem'
|
||||
export * from './Utilities/Item/ItemContentsDiffer'
|
||||
@@ -81,3 +127,4 @@ export * from './Utilities/Payload/PayloadContentsEqual'
|
||||
export * from './Utilities/Payload/PayloadsByAlternatingUuid'
|
||||
export * from './Utilities/Payload/PayloadsByDuplicating'
|
||||
export * from './Utilities/Payload/PayloadSplit'
|
||||
export * from './Syncable/KeySystemRootKey/KeySystemIdentifier'
|
||||
|
||||
Reference in New Issue
Block a user