mirror of
https://github.com/bitwarden/browser
synced 2026-01-09 12:03:33 +00:00
[PS-1092] Organization Service Observables (#3462)
* Update imports * Implement observables in a few places * Add tests * Get all clients working * Use _destroy * Address PR feedback * Address PR feedback * Address feedback
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "../../abstractions/organization/organization-api.service.abstraction";
|
||||
import { SyncService } from "../../abstractions/sync/sync.service.abstraction";
|
||||
import { OrganizationApiKeyType } from "../../enums/organizationApiKeyType";
|
||||
import { ImportDirectoryRequest } from "../../models/request/importDirectoryRequest";
|
||||
import { OrganizationSsoRequest } from "../../models/request/organization/organizationSsoRequest";
|
||||
@@ -28,7 +29,7 @@ import { PaymentResponse } from "../../models/response/paymentResponse";
|
||||
import { TaxInfoResponse } from "../../models/response/taxInfoResponse";
|
||||
|
||||
export class OrganizationApiService implements OrganizationApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
constructor(private apiService: ApiService, private syncService: SyncService) {}
|
||||
|
||||
async get(id: string): Promise<OrganizationResponse> {
|
||||
const r = await this.apiService.send("GET", "/organizations/" + id, null, true, true);
|
||||
@@ -80,6 +81,8 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
|
||||
async create(request: OrganizationCreateRequest): Promise<OrganizationResponse> {
|
||||
const r = await this.apiService.send("POST", "/organizations", request, true, true);
|
||||
// Forcing a sync will notify organization service that they need to repull
|
||||
await this.syncService.fullSync(true);
|
||||
return new OrganizationResponse(r);
|
||||
}
|
||||
|
||||
@@ -90,7 +93,9 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
|
||||
async save(id: string, request: OrganizationUpdateRequest): Promise<OrganizationResponse> {
|
||||
const r = await this.apiService.send("PUT", "/organizations/" + id, request, true, true);
|
||||
return new OrganizationResponse(r);
|
||||
const data = new OrganizationResponse(r);
|
||||
await this.syncService.fullSync(true);
|
||||
return data;
|
||||
}
|
||||
|
||||
async updatePayment(id: string, request: PaymentRequest): Promise<void> {
|
||||
@@ -144,7 +149,7 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
}
|
||||
|
||||
async verifyBank(id: string, request: VerifyBankRequest): Promise<void> {
|
||||
return this.apiService.send(
|
||||
await this.apiService.send(
|
||||
"POST",
|
||||
"/organizations/" + id + "/verify-bank",
|
||||
request,
|
||||
@@ -162,15 +167,17 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
}
|
||||
|
||||
async leave(id: string): Promise<void> {
|
||||
return this.apiService.send("POST", "/organizations/" + id + "/leave", null, true, false);
|
||||
await this.apiService.send("POST", "/organizations/" + id + "/leave", null, true, false);
|
||||
await this.syncService.fullSync(true);
|
||||
}
|
||||
|
||||
async delete(id: string, request: SecretVerificationRequest): Promise<void> {
|
||||
return this.apiService.send("DELETE", "/organizations/" + id, request, true, false);
|
||||
await this.apiService.send("DELETE", "/organizations/" + id, request, true, false);
|
||||
await this.syncService.fullSync(true);
|
||||
}
|
||||
|
||||
async updateLicense(id: string, data: FormData): Promise<void> {
|
||||
return this.apiService.send("POST", "/organizations/" + id + "/license", data, true, false);
|
||||
await this.apiService.send("POST", "/organizations/" + id + "/license", data, true, false);
|
||||
}
|
||||
|
||||
async importDirectory(organizationId: string, request: ImportDirectoryRequest): Promise<void> {
|
||||
@@ -223,6 +230,7 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
}
|
||||
|
||||
async updateTaxInfo(id: string, request: OrganizationTaxInfoUpdateRequest): Promise<void> {
|
||||
// Can't broadcast anything because the response doesn't have content
|
||||
return this.apiService.send("PUT", "/organizations/" + id + "/tax", request, true, false);
|
||||
}
|
||||
|
||||
@@ -242,6 +250,7 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
true,
|
||||
true
|
||||
);
|
||||
// Not broadcasting anything because data on this response doesn't correspond to `Organization`
|
||||
return new OrganizationKeysResponse(r);
|
||||
}
|
||||
|
||||
@@ -258,6 +267,7 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
true,
|
||||
true
|
||||
);
|
||||
// Not broadcasting anything because data on this response doesn't correspond to `Organization`
|
||||
return new OrganizationSsoResponse(r);
|
||||
}
|
||||
}
|
||||
|
||||
119
libs/common/src/services/organization/organization.service.ts
Normal file
119
libs/common/src/services/organization/organization.service.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { BehaviorSubject, concatMap, filter } from "rxjs";
|
||||
|
||||
import { OrganizationService as OrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { SyncNotifierService } from "../../abstractions/sync/syncNotifier.service.abstraction";
|
||||
import { OrganizationData } from "../../models/data/organizationData";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
import { isSuccessfullyCompleted } from "../../types/syncEventArgs";
|
||||
|
||||
export class OrganizationService implements OrganizationServiceAbstraction {
|
||||
private _organizations = new BehaviorSubject<Organization[]>([]);
|
||||
|
||||
organizations$ = this._organizations.asObservable();
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private syncNotifierService: SyncNotifierService
|
||||
) {
|
||||
this.stateService.activeAccountUnlocked$
|
||||
.pipe(
|
||||
concatMap(async (unlocked) => {
|
||||
if (!unlocked) {
|
||||
this._organizations.next([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await this.stateService.getOrganizations();
|
||||
this.updateObservables(data);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.syncNotifierService.sync$
|
||||
.pipe(
|
||||
filter(isSuccessfullyCompleted),
|
||||
concatMap(async ({ data }) => {
|
||||
const { profile } = data;
|
||||
const organizations: { [id: string]: OrganizationData } = {};
|
||||
profile.organizations.forEach((o) => {
|
||||
organizations[o.id] = new OrganizationData(o);
|
||||
});
|
||||
|
||||
profile.providerOrganizations.forEach((o) => {
|
||||
if (organizations[o.id] == null) {
|
||||
organizations[o.id] = new OrganizationData(o);
|
||||
organizations[o.id].isProviderUser = true;
|
||||
}
|
||||
});
|
||||
|
||||
await this.updateStateAndObservables(organizations);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
async getAll(userId?: string): Promise<Organization[]> {
|
||||
const organizationsMap = await this.stateService.getOrganizations({ userId: userId });
|
||||
return Object.values(organizationsMap || {}).map((o) => new Organization(o));
|
||||
}
|
||||
|
||||
async canManageSponsorships(): Promise<boolean> {
|
||||
const organizations = this._organizations.getValue();
|
||||
return organizations.some(
|
||||
(o) => o.familySponsorshipAvailable || o.familySponsorshipFriendlyName !== null
|
||||
);
|
||||
}
|
||||
|
||||
hasOrganizations(): boolean {
|
||||
const organizations = this._organizations.getValue();
|
||||
return organizations.length > 0;
|
||||
}
|
||||
|
||||
async upsert(organization: OrganizationData): Promise<void> {
|
||||
let organizations = await this.stateService.getOrganizations();
|
||||
if (organizations == null) {
|
||||
organizations = {};
|
||||
}
|
||||
|
||||
organizations[organization.id] = organization;
|
||||
|
||||
await this.updateStateAndObservables(organizations);
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
const organizations = await this.stateService.getOrganizations();
|
||||
if (organizations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (organizations[id] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete organizations[id];
|
||||
await this.updateStateAndObservables(organizations);
|
||||
}
|
||||
|
||||
get(id: string): Organization {
|
||||
const organizations = this._organizations.getValue();
|
||||
|
||||
return organizations.find((organization) => organization.id === id);
|
||||
}
|
||||
|
||||
getByIdentifier(identifier: string): Organization {
|
||||
const organizations = this._organizations.getValue();
|
||||
|
||||
return organizations.find((organization) => organization.identifier === identifier);
|
||||
}
|
||||
|
||||
private async updateStateAndObservables(organizationsMap: { [id: string]: OrganizationData }) {
|
||||
await this.stateService.setOrganizations(organizationsMap);
|
||||
this.updateObservables(organizationsMap);
|
||||
}
|
||||
|
||||
private updateObservables(organizationsMap: { [id: string]: OrganizationData }) {
|
||||
const organizations = Object.values(organizationsMap || {}).map((o) => new Organization(o));
|
||||
this._organizations.next(organizations);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user