mirror of
https://github.com/bitwarden/jslib
synced 2026-01-05 18:13:14 +00:00
WIP ngrx
This commit is contained in:
43
angular/package-lock.json
generated
43
angular/package-lock.json
generated
@@ -19,6 +19,8 @@
|
||||
"@angular/platform-browser-dynamic": "^12.2.13",
|
||||
"@angular/router": "^12.2.13",
|
||||
"@bitwarden/jslib-common": "file:../common",
|
||||
"@ngrx/entity": "^12.5.1",
|
||||
"@ngrx/store": "^12.5.1",
|
||||
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||
"rxjs": "^7.4.0",
|
||||
"tldjs": "^2.3.1",
|
||||
@@ -204,6 +206,31 @@
|
||||
"resolved": "../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@ngrx/entity": {
|
||||
"version": "12.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@ngrx/entity/-/entity-12.5.1.tgz",
|
||||
"integrity": "sha512-bjRMMe83onhrhxu5rJo+FhaS0gFY4HbMulSjUpuh0/LJckd0Z3QUDs+UcqYW/tjG/2o2rbNDxkws6w1D0c5ksA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "^12.0.0",
|
||||
"@ngrx/store": "12.5.1",
|
||||
"rxjs": "^6.5.3 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ngrx/store": {
|
||||
"version": "12.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-12.5.1.tgz",
|
||||
"integrity": "sha512-NLVkHLVeZc7IboXSDZlFoq1QrupmwYTYKRHS6se7ZasAv/lrIjHWsVVdICKSVRBsHZYu3+dmCXmu+YgulP7iHw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "^12.0.0",
|
||||
"rxjs": "^6.5.3 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/duo_web_sdk": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/duo_web_sdk/-/duo_web_sdk-2.7.1.tgz",
|
||||
@@ -490,6 +517,22 @@
|
||||
"zxcvbn": "^4.4.2"
|
||||
}
|
||||
},
|
||||
"@ngrx/entity": {
|
||||
"version": "12.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@ngrx/entity/-/entity-12.5.1.tgz",
|
||||
"integrity": "sha512-bjRMMe83onhrhxu5rJo+FhaS0gFY4HbMulSjUpuh0/LJckd0Z3QUDs+UcqYW/tjG/2o2rbNDxkws6w1D0c5ksA==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@ngrx/store": {
|
||||
"version": "12.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-12.5.1.tgz",
|
||||
"integrity": "sha512-NLVkHLVeZc7IboXSDZlFoq1QrupmwYTYKRHS6se7ZasAv/lrIjHWsVVdICKSVRBsHZYu3+dmCXmu+YgulP7iHw==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@types/duo_web_sdk": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/duo_web_sdk/-/duo_web_sdk-2.7.1.tgz",
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
"@angular/platform-browser-dynamic": "^12.2.13",
|
||||
"@angular/router": "^12.2.13",
|
||||
"@bitwarden/jslib-common": "file:../common",
|
||||
"@ngrx/entity": "^12.5.1",
|
||||
"@ngrx/store": "^12.5.1",
|
||||
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||
"rxjs": "^7.4.0",
|
||||
"tldjs": "^2.3.1",
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { Directive, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
import { Folder } from "jslib-common/models/domain/folder";
|
||||
import { TreeNode } from "jslib-common/models/domain/treeNode";
|
||||
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||
import { FolderDecrypted } from "jslib-common/models/view/folderDecrypted";
|
||||
import * as fromFolders from "jslib-common/state";
|
||||
|
||||
@Directive()
|
||||
export class GroupingsComponent {
|
||||
@@ -24,6 +28,8 @@ export class GroupingsComponent {
|
||||
@Output() onEditFolder = new EventEmitter<FolderDecrypted>();
|
||||
@Output() onCollectionClicked = new EventEmitter<CollectionView>();
|
||||
|
||||
folders$: Observable<Folder[]>;
|
||||
|
||||
folders: FolderDecrypted[];
|
||||
nestedFolders: TreeNode<FolderDecrypted>[];
|
||||
collections: CollectionView[];
|
||||
@@ -43,8 +49,12 @@ export class GroupingsComponent {
|
||||
constructor(
|
||||
protected collectionService: CollectionService,
|
||||
protected folderService: FolderService,
|
||||
protected stateService: StateService
|
||||
) {}
|
||||
protected stateService: StateService,
|
||||
store: Store
|
||||
) {
|
||||
this.folders$ = store.select(fromFolders.selectFolderCollection);
|
||||
console.log(this.folders$);
|
||||
}
|
||||
|
||||
async load(setLoaded = true) {
|
||||
const collapsedGroupings = await this.stateService.getCollapsedGroupings();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
|
||||
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
|
||||
@@ -73,6 +74,7 @@ import { UserVerificationService } from "jslib-common/services/userVerification.
|
||||
import { UsernameGenerationService } from "jslib-common/services/usernameGeneration.service";
|
||||
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
|
||||
import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service";
|
||||
import { State } from "jslib-common/state";
|
||||
|
||||
import { AuthGuardService } from "./auth-guard.service";
|
||||
import { BroadcasterService } from "./broadcaster.service";
|
||||
@@ -250,7 +252,8 @@ import { ValidationService } from "./validation.service";
|
||||
keyConnectorService: KeyConnectorServiceAbstraction,
|
||||
stateService: StateServiceAbstraction,
|
||||
organizationService: OrganizationServiceAbstraction,
|
||||
providerService: ProviderServiceAbstraction
|
||||
providerService: ProviderServiceAbstraction,
|
||||
store: Store
|
||||
) =>
|
||||
new SyncService(
|
||||
apiService,
|
||||
@@ -267,6 +270,7 @@ import { ValidationService } from "./validation.service";
|
||||
stateService,
|
||||
organizationService,
|
||||
providerService,
|
||||
store,
|
||||
async (expired: boolean) => messagingService.send("logout", { expired: expired })
|
||||
),
|
||||
deps: [
|
||||
@@ -284,6 +288,7 @@ import { ValidationService } from "./validation.service";
|
||||
StateServiceAbstraction,
|
||||
OrganizationServiceAbstraction,
|
||||
ProviderServiceAbstraction,
|
||||
Store,
|
||||
],
|
||||
},
|
||||
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
|
||||
import { Folder } from "../domain/folder";
|
||||
import { SymmetricCryptoKey } from "../domain/symmetricCryptoKey";
|
||||
import { ITreeNodeObject } from "../domain/treeNode";
|
||||
|
||||
export class FolderDecrypted implements ITreeNodeObject {
|
||||
@@ -8,10 +9,10 @@ export class FolderDecrypted implements ITreeNodeObject {
|
||||
name: string = null;
|
||||
revisionDate: Date = null;
|
||||
|
||||
async encrypt(cryptoService: CryptoService): Promise<Folder> {
|
||||
async encrypt(cryptoService: CryptoService, key?: SymmetricCryptoKey): Promise<Folder> {
|
||||
return {
|
||||
id: this.id,
|
||||
name: await cryptoService.encrypt(this.name),
|
||||
name: await cryptoService.encrypt(this.name, key),
|
||||
revisionDate: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Store } from "@ngrx/store";
|
||||
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
import { CipherService } from "../abstractions/cipher.service";
|
||||
import { CollectionService } from "../abstractions/collection.service";
|
||||
@@ -33,6 +35,8 @@ import {
|
||||
import { PolicyResponse } from "../models/response/policyResponse";
|
||||
import { ProfileResponse } from "../models/response/profileResponse";
|
||||
import { SendResponse } from "../models/response/sendResponse";
|
||||
import * as FoldersActions from "../state/folders.actions";
|
||||
|
||||
|
||||
export class SyncService implements SyncServiceAbstraction {
|
||||
syncInProgress = false;
|
||||
@@ -52,6 +56,7 @@ export class SyncService implements SyncServiceAbstraction {
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService,
|
||||
private providerService: ProviderService,
|
||||
private store: Store,
|
||||
private logoutCallback: (expired: boolean) => Promise<void>
|
||||
) {}
|
||||
|
||||
@@ -340,6 +345,12 @@ export class SyncService implements SyncServiceAbstraction {
|
||||
response.forEach((f) => {
|
||||
folders[f.id] = new FolderData(f.toFolder());
|
||||
});
|
||||
|
||||
console.log("hi");
|
||||
this.store.dispatch(
|
||||
FoldersActions.loadBooksSuccess({ folders: response.map((f) => f.toFolder()) })
|
||||
);
|
||||
|
||||
return await this.folderService.replace(folders);
|
||||
}
|
||||
|
||||
|
||||
51
common/src/state/collection.reducer.ts
Normal file
51
common/src/state/collection.reducer.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { createReducer, on } from "@ngrx/store";
|
||||
|
||||
import * as FolderActions from "./folders.actions";
|
||||
|
||||
export const collectionFeatureKey = "collection";
|
||||
|
||||
export interface State {
|
||||
loaded: boolean;
|
||||
ids: string[];
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
loaded: false,
|
||||
ids: [],
|
||||
};
|
||||
|
||||
export const reducer = createReducer(
|
||||
initialState,
|
||||
on(FolderActions.loadBooksSuccess, (_state, { folders }) => ({
|
||||
loaded: true,
|
||||
ids: folders.map((f) => f.id),
|
||||
})),
|
||||
/**
|
||||
* Optimistically add book to collection.
|
||||
* If this succeeds there's nothing to do.
|
||||
* If this fails we revert state by removing the book.
|
||||
*
|
||||
* `on` supports handling multiple types of actions
|
||||
*/
|
||||
on(FolderActions.addFolder, (state, { folder }) => {
|
||||
if (state.ids.indexOf(folder.id) > -1) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
ids: [...state.ids, folder.id],
|
||||
};
|
||||
}),
|
||||
/**
|
||||
* Optimistically remove book from collection.
|
||||
* If addBook fails, we "undo" adding the book.
|
||||
*/
|
||||
on(FolderActions.removeFolder, (state, { folder }) => ({
|
||||
...state,
|
||||
ids: state.ids.filter((id) => id !== folder.id),
|
||||
}))
|
||||
);
|
||||
|
||||
export const getLoaded = (state: State) => state.loaded;
|
||||
|
||||
export const getIds = (state: State) => state.ids;
|
||||
7
common/src/state/folder.ts
Normal file
7
common/src/state/folder.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { EncString } from "jslib-common/models/domain/encString";
|
||||
|
||||
export interface Folder {
|
||||
id: string;
|
||||
name: EncString;
|
||||
revisionDate: Date;
|
||||
}
|
||||
17
common/src/state/folders.actions.ts
Normal file
17
common/src/state/folders.actions.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createAction, props } from "@ngrx/store";
|
||||
|
||||
import { Folder } from "./folder";
|
||||
|
||||
export const addFolder = createAction("[AddEdit Folder] Add Folder", props<{ folder: Folder }>());
|
||||
|
||||
export const editFolder = createAction("[AddEdit Folder] Edit Folder", props<{ folder: Folder }>());
|
||||
|
||||
export const removeFolder = createAction(
|
||||
"[AddEdit Folder] Remove Folder",
|
||||
props<{ folder: Folder }>()
|
||||
);
|
||||
|
||||
export const loadBooksSuccess = createAction(
|
||||
"[Sync API] Loaded Folders Success",
|
||||
props<{ folders: Folder[] }>()
|
||||
);
|
||||
20
common/src/state/folders.reducer.ts
Normal file
20
common/src/state/folders.reducer.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createEntityAdapter, EntityAdapter, EntityState } from "@ngrx/entity";
|
||||
import { createReducer, on } from "@ngrx/store";
|
||||
|
||||
import { Folder } from "./folder";
|
||||
import * as FolderActions from "./folders.actions";
|
||||
|
||||
export const foldersFeatureKey = "folders";
|
||||
|
||||
export type State = EntityState<Folder>
|
||||
|
||||
export const adapter: EntityAdapter<Folder> = createEntityAdapter<Folder>({
|
||||
sortComparer: false,
|
||||
});
|
||||
|
||||
export const initialState: State = adapter.getInitialState({});
|
||||
|
||||
export const reducer = createReducer(
|
||||
initialState,
|
||||
on(FolderActions.loadBooksSuccess, (state, { folders }) => adapter.addMany(folders, state))
|
||||
);
|
||||
47
common/src/state/index.ts
Normal file
47
common/src/state/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createSelector, createFeatureSelector } from "@ngrx/store";
|
||||
|
||||
import * as fromCollection from "./collection.reducer";
|
||||
import { Folder } from "./folder";
|
||||
import * as fromBooks from "./folders.reducer";
|
||||
|
||||
export interface State {
|
||||
[fromBooks.foldersFeatureKey]: fromBooks.State;
|
||||
[fromCollection.collectionFeatureKey]: fromCollection.State;
|
||||
}
|
||||
|
||||
export const reducers = {
|
||||
[fromBooks.foldersFeatureKey]: fromBooks.reducer,
|
||||
[fromCollection.collectionFeatureKey]: fromCollection.reducer,
|
||||
};
|
||||
|
||||
export const selectFoldersState = createFeatureSelector<State>(fromBooks.foldersFeatureKey);
|
||||
|
||||
export const selectFolderEntitiesState = createSelector(
|
||||
selectFoldersState,
|
||||
(state) => state.folders
|
||||
);
|
||||
|
||||
export const {
|
||||
selectIds: selectFolderIds,
|
||||
selectEntities: selectFolderEntities,
|
||||
selectAll: selectAllFolders,
|
||||
selectTotal: selectTotalFolders,
|
||||
} = fromBooks.adapter.getSelectors(selectFolderEntitiesState);
|
||||
|
||||
export const selectCollectionState = createSelector(
|
||||
selectFoldersState,
|
||||
(state) => state.collection
|
||||
);
|
||||
|
||||
export const selectCollectionFolderIds = createSelector(
|
||||
selectCollectionState,
|
||||
fromCollection.getIds
|
||||
);
|
||||
|
||||
export const selectFolderCollection = createSelector(
|
||||
selectFolderEntities,
|
||||
selectCollectionFolderIds,
|
||||
(entities, ids) => {
|
||||
return ids.map((id) => entities[id]).filter((folder): folder is Folder => folder != null);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user