mirror of
https://github.com/bitwarden/browser.git
synced 2025-03-02 03:41:09 +01:00
Move state stuff to a single location
This commit is contained in:
parent
e5889f2280
commit
dfdde4daba
@ -92,12 +92,12 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
|
||||
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||
import { DefaultUserStateProvider } from "@bitwarden/common/platform/services/default-user-state.provider";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
|
||||
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
||||
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/services/validation.service";
|
||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||
import { DefaultUserStateProvider } from "@bitwarden/common/platform/state";
|
||||
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
||||
import { AnonymousHubService } from "@bitwarden/common/services/anonymousHub.service";
|
||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { GlobalState } from "../interfaces/global-state";
|
||||
import { KeyDefinition } from "../types/key-definition";
|
||||
import { KeyDefinition } from "../state/key-definition";
|
||||
|
||||
export abstract class GlobalStateProvider {
|
||||
create: <T>(keyDefinition: KeyDefinition<T>) => GlobalState<T>;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { UserState } from "../interfaces/user-state";
|
||||
import { KeyDefinition } from "../types/key-definition";
|
||||
import { KeyDefinition } from "../state/key-definition";
|
||||
|
||||
export abstract class UserStateProvider {
|
||||
create: <T>(keyDefinition: KeyDefinition<T>) => UserState<T>;
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { UserId } from "../../types/guid";
|
||||
import { DerivedUserState } from "../services/default-user-state.provider";
|
||||
import { DerivedStateDefinition } from "../types/derived-state-definition";
|
||||
import { DerivedStateDefinition , DerivedUserState } from "../state/";
|
||||
|
||||
export interface UserState<T> {
|
||||
readonly state$: Observable<T>;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { KeyDefinition } from "../types/key-definition";
|
||||
import { KeyDefinition } from "../state/key-definition";
|
||||
|
||||
// TODO: Use Matts `UserId` type
|
||||
export function userKeyBuilder(userId: string, keyDefinition: KeyDefinition<unknown>): string {
|
||||
|
@ -1,89 +0,0 @@
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { GlobalStateProvider } from "../abstractions/global-state.provider";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
} from "../abstractions/storage.service";
|
||||
import { GlobalState } from "../interfaces/global-state";
|
||||
import { globalKeyBuilder } from "../misc/key-builders";
|
||||
import { KeyDefinition } from "../types/key-definition";
|
||||
|
||||
// TODO: Move type
|
||||
export type StorageLocation = "memory" | "disk" | "secure";
|
||||
|
||||
class GlobalStateImplementation<T> implements GlobalState<T> {
|
||||
private storageKey: string;
|
||||
private seededPromise: Promise<void>;
|
||||
|
||||
protected stateSubject: BehaviorSubject<T | null> = new BehaviorSubject<T | null>(null);
|
||||
|
||||
state$: Observable<T>;
|
||||
|
||||
constructor(
|
||||
private keyDefinition: KeyDefinition<T>,
|
||||
private chosenLocation: AbstractStorageService
|
||||
) {
|
||||
this.storageKey = globalKeyBuilder(this.keyDefinition);
|
||||
|
||||
this.seededPromise = this.chosenLocation.get<Jsonify<T>>(this.storageKey).then((data) => {
|
||||
const serializedData = this.keyDefinition.deserializer(data);
|
||||
this.stateSubject.next(serializedData);
|
||||
});
|
||||
|
||||
this.state$ = this.stateSubject.asObservable();
|
||||
}
|
||||
|
||||
async update(configureState: (state: T) => void): Promise<void> {
|
||||
await this.seededPromise;
|
||||
const currentState = this.stateSubject.getValue();
|
||||
configureState(currentState);
|
||||
await this.chosenLocation.save(this.storageKey, currentState);
|
||||
this.stateSubject.next(currentState);
|
||||
}
|
||||
|
||||
async getFromState(): Promise<T> {
|
||||
const data = await this.chosenLocation.get<Jsonify<T>>(this.storageKey);
|
||||
return this.keyDefinition.deserializer(data);
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
||||
private globalStateCache: Record<string, GlobalState<unknown>> = {};
|
||||
|
||||
constructor(
|
||||
private memoryStorage: AbstractMemoryStorageService,
|
||||
private diskStorage: AbstractStorageService,
|
||||
private secureStorage: AbstractStorageService
|
||||
) {}
|
||||
|
||||
create<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
||||
const locationDomainKey = `${keyDefinition.stateDefinition.storageLocation}_${keyDefinition.stateDefinition.name}_${keyDefinition.key}`;
|
||||
const existingGlobalState = this.globalStateCache[locationDomainKey];
|
||||
if (existingGlobalState != null) {
|
||||
// I have to cast out of the unknown generic but this should be safe if rules
|
||||
// around domain token are made
|
||||
return existingGlobalState as GlobalStateImplementation<T>;
|
||||
}
|
||||
|
||||
const newGlobalState = new GlobalStateImplementation<T>(
|
||||
keyDefinition,
|
||||
this.getLocation(keyDefinition.stateDefinition.storageLocation)
|
||||
);
|
||||
|
||||
this.globalStateCache[locationDomainKey] = newGlobalState;
|
||||
return newGlobalState;
|
||||
}
|
||||
|
||||
private getLocation(location: StorageLocation) {
|
||||
switch (location) {
|
||||
case "disk":
|
||||
return this.diskStorage;
|
||||
case "secure":
|
||||
return this.secureStorage;
|
||||
case "memory":
|
||||
return this.memoryStorage;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { GlobalStateProvider } from "../abstractions/global-state.provider";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
} from "../abstractions/storage.service";
|
||||
import { GlobalState } from "../interfaces/global-state";
|
||||
|
||||
import { DefaultGlobalState } from "./default-global-state";
|
||||
import { KeyDefinition } from "./key-definition";
|
||||
import { StorageLocation } from "./state-definition";
|
||||
|
||||
export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
||||
private globalStateCache: Record<string, GlobalState<unknown>> = {};
|
||||
|
||||
constructor(
|
||||
private memoryStorage: AbstractMemoryStorageService,
|
||||
private diskStorage: AbstractStorageService,
|
||||
private secureStorage: AbstractStorageService
|
||||
) {}
|
||||
|
||||
create<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
||||
const locationDomainKey = `${keyDefinition.stateDefinition.storageLocation}_${keyDefinition.stateDefinition.name}_${keyDefinition.key}`;
|
||||
const existingGlobalState = this.globalStateCache[locationDomainKey];
|
||||
if (existingGlobalState != null) {
|
||||
// I have to cast out of the unknown generic but this should be safe if rules
|
||||
// around domain token are made
|
||||
return existingGlobalState as DefaultGlobalState<T>;
|
||||
}
|
||||
|
||||
const newGlobalState = new DefaultGlobalState<T>(
|
||||
keyDefinition,
|
||||
this.getLocation(keyDefinition.stateDefinition.storageLocation)
|
||||
);
|
||||
|
||||
this.globalStateCache[locationDomainKey] = newGlobalState;
|
||||
return newGlobalState;
|
||||
}
|
||||
|
||||
private getLocation(location: StorageLocation) {
|
||||
switch (location) {
|
||||
case "disk":
|
||||
return this.diskStorage;
|
||||
case "secure":
|
||||
return this.secureStorage;
|
||||
case "memory":
|
||||
return this.memoryStorage;
|
||||
}
|
||||
}
|
||||
}
|
44
libs/common/src/platform/state/default-global-state.ts
Normal file
44
libs/common/src/platform/state/default-global-state.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { AbstractStorageService } from "../abstractions/storage.service";
|
||||
import { GlobalState } from "../interfaces/global-state";
|
||||
import { globalKeyBuilder } from "../misc/key-builders";
|
||||
|
||||
import { KeyDefinition } from "./key-definition";
|
||||
|
||||
export class DefaultGlobalState<T> implements GlobalState<T> {
|
||||
private storageKey: string;
|
||||
private seededPromise: Promise<void>;
|
||||
|
||||
protected stateSubject: BehaviorSubject<T | null> = new BehaviorSubject<T | null>(null);
|
||||
|
||||
state$: Observable<T>;
|
||||
|
||||
constructor(
|
||||
private keyDefinition: KeyDefinition<T>,
|
||||
private chosenLocation: AbstractStorageService
|
||||
) {
|
||||
this.storageKey = globalKeyBuilder(this.keyDefinition);
|
||||
|
||||
this.seededPromise = this.chosenLocation.get<Jsonify<T>>(this.storageKey).then((data) => {
|
||||
const serializedData = this.keyDefinition.deserializer(data);
|
||||
this.stateSubject.next(serializedData);
|
||||
});
|
||||
|
||||
this.state$ = this.stateSubject.asObservable();
|
||||
}
|
||||
|
||||
async update(configureState: (state: T) => void): Promise<void> {
|
||||
await this.seededPromise;
|
||||
const currentState = this.stateSubject.getValue();
|
||||
configureState(currentState);
|
||||
await this.chosenLocation.save(this.storageKey, currentState);
|
||||
this.stateSubject.next(currentState);
|
||||
}
|
||||
|
||||
async getFromState(): Promise<T> {
|
||||
const data = await this.chosenLocation.get<Jsonify<T>>(this.storageKey);
|
||||
return this.keyDefinition.deserializer(data);
|
||||
}
|
||||
}
|
@ -6,8 +6,8 @@ import { AccountInfo, AccountService } from "../../auth/abstractions/account.ser
|
||||
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { AbstractMemoryStorageService } from "../abstractions/storage.service";
|
||||
import { KeyDefinition } from "../types/key-definition";
|
||||
import { StateDefinition } from "../types/state-definition";
|
||||
import { KeyDefinition } from "../state/key-definition";
|
||||
import { StateDefinition } from "../state/state-definition";
|
||||
|
||||
import { DefaultUserStateProvider } from "./default-user-state.provider";
|
||||
|
@ -0,0 +1,42 @@
|
||||
import { AccountService } from "../../auth/abstractions/account.service";
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
} from "../abstractions/storage.service";
|
||||
import { UserStateProvider } from "../abstractions/user-state.provider";
|
||||
import { DefaultUserState } from "../state/default-user-state";
|
||||
import { KeyDefinition } from "../state/key-definition";
|
||||
|
||||
export class DefaultUserStateProvider implements UserStateProvider {
|
||||
private userStateCache: Record<string, DefaultUserState<unknown>> = {};
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService, // Inject the lightest weight service that provides accountUserId$
|
||||
private encryptService: EncryptService,
|
||||
private memoryStorage: AbstractMemoryStorageService,
|
||||
private diskStorage: AbstractStorageService,
|
||||
private secureStorage: AbstractStorageService
|
||||
) {}
|
||||
|
||||
create<T>(keyDefinition: KeyDefinition<T>): DefaultUserState<T> {
|
||||
const locationDomainKey = `${keyDefinition.stateDefinition.storageLocation}_${keyDefinition.stateDefinition.name}_${keyDefinition.key}`;
|
||||
const existingUserState = this.userStateCache[locationDomainKey];
|
||||
if (existingUserState != null) {
|
||||
// I have to cast out of the unknown generic but this should be safe if rules
|
||||
// around domain token are made
|
||||
return existingUserState as DefaultUserState<T>;
|
||||
}
|
||||
|
||||
const newUserState = new DefaultUserState<T>(
|
||||
keyDefinition,
|
||||
this.accountService,
|
||||
this.encryptService,
|
||||
this.memoryStorage,
|
||||
this.secureStorage,
|
||||
this.diskStorage
|
||||
);
|
||||
this.userStateCache[locationDomainKey] = newUserState;
|
||||
return newUserState;
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import {
|
||||
BehaviorSubject,
|
||||
Observable,
|
||||
defer,
|
||||
firstValueFrom,
|
||||
BehaviorSubject,
|
||||
map,
|
||||
share,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
tap,
|
||||
share,
|
||||
defer,
|
||||
firstValueFrom,
|
||||
} from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
@ -15,52 +15,18 @@ import { AccountService } from "../../auth/abstractions/account.service";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
AbstractMemoryStorageService,
|
||||
} from "../abstractions/storage.service";
|
||||
import { UserStateProvider } from "../abstractions/user-state.provider";
|
||||
import { UserState } from "../interfaces/user-state";
|
||||
import { userKeyBuilder } from "../misc/key-builders";
|
||||
import { DeriveContext, DerivedStateDefinition } from "../types/derived-state-definition";
|
||||
import { KeyDefinition } from "../types/key-definition";
|
||||
|
||||
import { StorageLocation } from "./default-global-state.provider";
|
||||
import { DerivedStateDefinition } from "./derived-state-definition";
|
||||
import { DerivedUserState } from "./derived-user-state";
|
||||
import { KeyDefinition } from "./key-definition";
|
||||
import { StorageLocation } from "./state-definition";
|
||||
|
||||
export class DerivedUserState<TFrom, TTo> {
|
||||
state$: Observable<TTo>;
|
||||
|
||||
// TODO: Probably needs to take state service
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
private derivedStateDefinition: DerivedStateDefinition<TFrom, TTo>,
|
||||
private encryptService: EncryptService,
|
||||
private userState: UserState<TFrom>
|
||||
) {
|
||||
this.state$ = userState.state$.pipe(
|
||||
switchMap(async (from) => {
|
||||
// TODO: How do I get the key?
|
||||
const convertedData = await derivedStateDefinition.converter(
|
||||
from,
|
||||
new DeriveContext(null, encryptService)
|
||||
);
|
||||
return convertedData;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async getFromState(): Promise<TTo> {
|
||||
const encryptedFromState = await this.userState.getFromState();
|
||||
|
||||
const context = new DeriveContext(null, this.encryptService);
|
||||
|
||||
const decryptedData = await this.derivedStateDefinition.converter(encryptedFromState, context);
|
||||
return decryptedData;
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultUserState<T> implements UserState<T> {
|
||||
export class DefaultUserState<T> implements UserState<T> {
|
||||
private seededInitial = false;
|
||||
|
||||
private formattedKey$: Observable<string>;
|
||||
@ -192,36 +158,3 @@ class DefaultUserState<T> implements UserState<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultUserStateProvider implements UserStateProvider {
|
||||
private userStateCache: Record<string, DefaultUserState<unknown>> = {};
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService, // Inject the lightest weight service that provides accountUserId$
|
||||
private encryptService: EncryptService,
|
||||
private memoryStorage: AbstractMemoryStorageService,
|
||||
private diskStorage: AbstractStorageService,
|
||||
private secureStorage: AbstractStorageService
|
||||
) {}
|
||||
|
||||
create<T>(keyDefinition: KeyDefinition<T>): DefaultUserState<T> {
|
||||
const locationDomainKey = `${keyDefinition.stateDefinition.storageLocation}_${keyDefinition.stateDefinition.name}_${keyDefinition.key}`;
|
||||
const existingUserState = this.userStateCache[locationDomainKey];
|
||||
if (existingUserState != null) {
|
||||
// I have to cast out of the unknown generic but this should be safe if rules
|
||||
// around domain token are made
|
||||
return existingUserState as DefaultUserState<T>;
|
||||
}
|
||||
|
||||
const newUserState = new DefaultUserState<T>(
|
||||
keyDefinition,
|
||||
this.accountService,
|
||||
this.encryptService,
|
||||
this.memoryStorage,
|
||||
this.secureStorage,
|
||||
this.diskStorage
|
||||
);
|
||||
this.userStateCache[locationDomainKey] = newUserState;
|
||||
return newUserState;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
import { UserKey } from "../models/domain/symmetric-crypto-key";
|
||||
import { StorageLocation } from "../services/default-global-state.provider";
|
||||
|
||||
import { StorageLocation } from "./state-definition";
|
||||
|
||||
// TODO: Move type
|
||||
export class DeriveContext {
|
40
libs/common/src/platform/state/derived-user-state.ts
Normal file
40
libs/common/src/platform/state/derived-user-state.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Observable, switchMap } from "rxjs";
|
||||
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
import { UserState } from "../interfaces/user-state";
|
||||
|
||||
import { DerivedStateDefinition, DeriveContext } from "./derived-state-definition";
|
||||
|
||||
export class DerivedUserState<TFrom, TTo> {
|
||||
state$: Observable<TTo>;
|
||||
|
||||
// TODO: Probably needs to take state service
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
private derivedStateDefinition: DerivedStateDefinition<TFrom, TTo>,
|
||||
private encryptService: EncryptService,
|
||||
private userState: UserState<TFrom>
|
||||
) {
|
||||
this.state$ = userState.state$.pipe(
|
||||
switchMap(async (from) => {
|
||||
// TODO: How do I get the key?
|
||||
const convertedData = await derivedStateDefinition.converter(
|
||||
from,
|
||||
new DeriveContext(null, encryptService)
|
||||
);
|
||||
return convertedData;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async getFromState(): Promise<TTo> {
|
||||
const encryptedFromState = await this.userState.getFromState();
|
||||
|
||||
const context = new DeriveContext(null, this.encryptService);
|
||||
|
||||
const decryptedData = await this.derivedStateDefinition.converter(encryptedFromState, context);
|
||||
return decryptedData;
|
||||
}
|
||||
}
|
13
libs/common/src/platform/state/index.ts
Normal file
13
libs/common/src/platform/state/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
// Export all established state definitions
|
||||
export * from "./state-definitions";
|
||||
// Export all established KeyDefinitions
|
||||
export * from "./key-definitions";
|
||||
export { DefaultGlobalStateProvider } from "./default-global-state.provider";
|
||||
export { DefaultGlobalState } from "./default-global-state";
|
||||
export { DefaultUserStateProvider } from "./default-user-state.provider";
|
||||
export { DefaultUserState } from "./default-user-state";
|
||||
export { DerivedStateDefinition } from "./derived-state-definition";
|
||||
export { DerivedUserState } from "./derived-user-state";
|
||||
// TODO: should this be exported?
|
||||
export { KeyDefinition } from "./key-definition";
|
||||
export { StorageLocation } from "./state-definition";
|
11
libs/common/src/platform/state/key-definitions.ts
Normal file
11
libs/common/src/platform/state/key-definitions.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { FolderData } from "../../vault/models/data/folder.data";
|
||||
|
||||
import { KeyDefinition } from "./key-definition";
|
||||
import { FOLDER_SERVICE_DISK } from "./state-definitions";
|
||||
|
||||
// FolderService Keys
|
||||
export const FOLDERS = KeyDefinition.record<FolderData>(
|
||||
FOLDER_SERVICE_DISK,
|
||||
"folders",
|
||||
FolderData.fromJSON
|
||||
);
|
@ -3,10 +3,10 @@
|
||||
import { userKeyBuilder } from "../../platform/misc/key-builders";
|
||||
// TODO: Add message
|
||||
// eslint-disable-next-line import/no-restricted-paths
|
||||
import { KeyDefinition } from "../../platform/types/key-definition";
|
||||
import { KeyDefinition } from "../../platform/state/key-definition";
|
||||
// TODO: Add message
|
||||
// eslint-disable-next-line import/no-restricted-paths
|
||||
import { StateDefinition } from "../../platform/types/state-definition";
|
||||
import { StateDefinition } from "../../platform/state/state-definition";
|
||||
import { MigrationHelper } from "../migration-helper";
|
||||
import { IRREVERSIBLE, Migrator } from "../migrator";
|
||||
|
||||
|
@ -7,7 +7,8 @@ import { UserStateProvider } from "../../../platform/abstractions/user-state.pro
|
||||
import { UserState } from "../../../platform/interfaces/user-state";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { DerivedUserState } from "../../../platform/services/default-user-state.provider";
|
||||
import { DerivedUserState } from "../../../platform/state";
|
||||
import { FOLDERS } from "../../../platform/state/key-definitions";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
||||
import { InternalFolderService as InternalFolderServiceAbstraction } from "../../../vault/abstractions/folder/folder.service.abstraction";
|
||||
@ -15,7 +16,6 @@ import { CipherData } from "../../../vault/models/data/cipher.data";
|
||||
import { FolderData } from "../../../vault/models/data/folder.data";
|
||||
import { Folder } from "../../../vault/models/domain/folder";
|
||||
import { FolderView } from "../../../vault/models/view/folder.view";
|
||||
import { FOLDERS } from "../../types/key-definitions";
|
||||
|
||||
export class FolderService implements InternalFolderServiceAbstraction {
|
||||
folderState: UserState<Record<string, FolderData>>;
|
||||
|
@ -1,10 +0,0 @@
|
||||
import { KeyDefinition } from "../../platform/types/key-definition";
|
||||
import { FOLDER_SERVICE_DISK } from "../../platform/types/state-definitions";
|
||||
import { FolderData } from "../models/data/folder.data";
|
||||
|
||||
// FolderService Keys
|
||||
export const FOLDERS = KeyDefinition.record<FolderData>(
|
||||
FOLDER_SERVICE_DISK,
|
||||
"folders",
|
||||
FolderData.fromJSON
|
||||
);
|
Loading…
Reference in New Issue
Block a user