1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-5979] Refactor EnvironmentService (#8040)

Refactor environment service to emit a single observable. This required significant changes to how the environment service behaves and tackles much of the tech debt planned for it.
This commit is contained in:
Oscar Hinton
2024-03-21 17:09:44 +01:00
committed by GitHub
parent 7a42b4ebc6
commit e767295c86
88 changed files with 1710 additions and 1379 deletions

View File

@@ -10,6 +10,15 @@
"proxyNotifications": "http://localhost:61840",
"wsConnectSrc": "ws://localhost:61840"
},
"additionalRegions": [
{
"key": "LOCAL",
"domain": "localhost",
"urls": {
"webVault": "https://localhost:8080"
}
}
],
"flags": {
"secretsManager": true,
"showPasswordless": true,

View File

@@ -4,6 +4,22 @@
"notifications": "https://notifications.euqa.bitwarden.pw",
"scim": "https://scim.euqa.bitwarden.pw"
},
"additionalRegions": [
{
"key": "USQA",
"domain": "qa.bitwarden.pw",
"urls": {
"webVault": "https://vault.qa.bitwarden.pw"
}
},
{
"key": "EUQA",
"domain": "euqa.bitwarden.pw",
"urls": {
"webVault": "https://vault.euqa.bitwarden.pw"
}
}
],
"flags": {
"secretsManager": true,
"showPasswordless": true

View File

@@ -10,6 +10,22 @@
"proxyEvents": "https://events.qa.bitwarden.pw",
"proxyNotifications": "https://notifications.qa.bitwarden.pw"
},
"additionalRegions": [
{
"key": "USQA",
"domain": "qa.bitwarden.pw",
"urls": {
"webVault": "https://vault.qa.bitwarden.pw"
}
},
{
"key": "EUQA",
"domain": "euqa.bitwarden.pw",
"urls": {
"webVault": "https://vault.euqa.bitwarden.pw"
}
}
],
"flags": {
"secretsManager": true,
"showPasswordless": true,

View File

@@ -127,7 +127,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
await this.submit();
};
override launchDuoFrameless() {
override async launchDuoFrameless() {
const duoHandOffMessage = {
title: this.i18nService.t("youSuccessfullyLoggedIn"),
message: this.i18nService.t("thisWindowWillCloseIn5Seconds"),

View File

@@ -44,11 +44,11 @@ export class PremiumComponent implements OnInit {
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {
this.selfHosted = platformUtilsService.isSelfHost();
this.cloudWebVaultUrl = this.environmentService.getCloudWebVaultUrl();
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
}
async ngOnInit() {
this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$);
if (await firstValueFrom(this.billingAccountProfileStateService.hasPremiumPersonally$)) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises

View File

@@ -49,10 +49,10 @@ export class UserSubscriptionComponent implements OnInit {
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {
this.selfHosted = platformUtilsService.isSelfHost();
this.cloudWebVaultUrl = this.environmentService.getCloudWebVaultUrl();
}
async ngOnInit() {
this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$);
this.presentUserWithOffboardingSurvey$ = this.configService.getFeatureFlag$<boolean>(
FeatureFlag.AC1607_PresentUserOffboardingSurvey,
);

View File

@@ -1,7 +1,7 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs";
import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
@@ -82,11 +82,11 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest
private i18nService: I18nService,
private environmentService: EnvironmentService,
private dialogService: DialogService,
) {
this.cloudWebVaultUrl = this.environmentService.getCloudWebVaultUrl();
}
) {}
async ngOnInit() {
this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$);
this.route.params
.pipe(
concatMap(async (params) => {

View File

@@ -14,11 +14,15 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { PayPalConfig } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
export type PayPalConfig = {
businessId?: string;
buttonAction?: string;
};
@Component({
selector: "app-add-credit",
templateUrl: "add-credit.component.html",

View File

@@ -1,38 +1,25 @@
<div class="tw-mb-1" *ngIf="showRegionSelector">
<bit-menu #environmentOptions>
<a
*ngFor="let region of availableRegions"
bitMenuItem
[attr.href]="
isUsServer ? 'javascript:void(0)' : 'https://vault.bitwarden.com' + routeAndParams
region == currentRegion ? 'javascript:void(0)' : region.urls.webVault + routeAndParams
"
class="pr-4"
>
<i
class="bwi bwi-fw bwi-sm bwi-check pb-1"
aria-hidden="true"
[style.visibility]="isUsServer ? 'visible' : 'hidden'"
[style.visibility]="region == currentRegion ? 'visible' : 'hidden'"
></i>
{{ "usDomain" | i18n }}
</a>
<a
bitMenuItem
[attr.href]="
isEuServer ? 'javascript:void(0)' : 'https://vault.bitwarden.eu' + routeAndParams
"
class="pr-4"
>
<i
class="bwi bwi-fw bwi-sm bwi-check pb-1"
aria-hidden="true"
[style.visibility]="isEuServer ? 'visible' : 'hidden'"
></i>
{{ "euDomain" | i18n }}
{{ region.domain }}
</a>
</bit-menu>
<div>
{{ "server" | i18n }}:
<a [routerLink]="[]" [bitMenuTriggerFor]="environmentOptions">
<b>{{ isEuServer ? ("euDomain" | i18n) : ("usDomain" | i18n) }}</b
<b>{{ currentRegion?.domain }}</b
><i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
</a>
</div>

View File

@@ -1,7 +1,10 @@
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { RegionDomain } from "@bitwarden/common/platform/abstractions/environment.service";
import {
EnvironmentService,
RegionConfig,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -12,19 +15,21 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
export class EnvironmentSelectorComponent implements OnInit {
constructor(
private platformUtilsService: PlatformUtilsService,
private environmentService: EnvironmentService,
private router: Router,
) {}
isEuServer: boolean;
isUsServer: boolean;
showRegionSelector = false;
routeAndParams: string;
protected availableRegions = this.environmentService.availableRegions();
protected currentRegion?: RegionConfig;
protected showRegionSelector = false;
protected routeAndParams: string;
async ngOnInit() {
const domain = Utils.getDomain(window.location.href);
this.isEuServer = domain.includes(RegionDomain.EU);
this.isUsServer = domain.includes(RegionDomain.US) || domain.includes(RegionDomain.USQA);
this.showRegionSelector = !this.platformUtilsService.isSelfHost();
this.routeAndParams = `/#${this.router.url}`;
const host = Utils.getHost(window.location.href);
this.currentRegion = this.availableRegions.find((r) => Utils.getHost(r.urls.webVault) === host);
}
}

View File

@@ -11,11 +11,14 @@ import {
OBSERVABLE_MEMORY_STORAGE,
OBSERVABLE_DISK_STORAGE,
OBSERVABLE_DISK_LOCAL_STORAGE,
WINDOW,
} from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
import { LoginService } from "@bitwarden/common/auth/services/login.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -28,9 +31,9 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state";
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
import {
@@ -41,6 +44,7 @@ import {
import { PolicyListService } from "../admin-console/core/policy-list.service";
import { HtmlStorageService } from "../core/html-storage.service";
import { I18nService } from "../core/i18n.service";
import { WebEnvironmentService } from "../platform/web-environment.service";
import { WebMigrationRunner } from "../platform/web-migration-runner";
import { WebStorageServiceProvider } from "../platform/web-storage-service.provider";
import { WindowStorageService } from "../platform/window-storage.service";
@@ -138,6 +142,11 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
OBSERVABLE_DISK_LOCAL_STORAGE,
],
},
{
provide: EnvironmentService,
useClass: WebEnvironmentService,
deps: [WINDOW, StateProvider, AccountService],
},
{
provide: ThemeStateService,
useFactory: (globalStateProvider: GlobalStateProvider) =>

View File

@@ -8,10 +8,6 @@ import { NotificationsService as NotificationsServiceAbstraction } from "@bitwar
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import {
EnvironmentService as EnvironmentServiceAbstraction,
Urls,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { ConfigService } from "@bitwarden/common/platform/services/config/config.service";
@@ -23,7 +19,6 @@ import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/va
export class InitService {
constructor(
@Inject(WINDOW) private win: Window,
private environmentService: EnvironmentServiceAbstraction,
private notificationsService: NotificationsServiceAbstraction,
private vaultTimeoutService: VaultTimeoutService,
private i18nService: I18nServiceAbstraction,
@@ -41,13 +36,6 @@ export class InitService {
return async () => {
await this.stateService.init();
const urls = process.env.URLS as Urls;
urls.base ??= this.win.location.origin;
await this.environmentService.setUrls(urls);
// Workaround to ignore stateService.activeAccount until process.env.URLS are set
// TODO: Remove this when implementing ticket PM-2637
this.environmentService.initialized = true;
setTimeout(() => this.notificationsService.init(), 3000);
await this.vaultTimeoutService.init(true);
await this.i18nService.init();

View File

@@ -0,0 +1,62 @@
import { ReplaySubject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
Environment,
Region,
RegionConfig,
Urls,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import {
CloudEnvironment,
DefaultEnvironmentService,
SelfHostedEnvironment,
} from "@bitwarden/common/platform/services/default-environment.service";
import { StateProvider } from "@bitwarden/common/platform/state";
/**
* Web specific environment service. Ensures that the urls are set from the window location.
*/
export class WebEnvironmentService extends DefaultEnvironmentService {
constructor(
private win: Window,
stateProvider: StateProvider,
accountService: AccountService,
) {
super(stateProvider, accountService);
// The web vault always uses the current location as the base url
const urls = process.env.URLS as Urls;
urls.base ??= this.win.location.origin;
// Find the region
const domain = Utils.getDomain(this.win.location.href);
const region = this.availableRegions().find((r) => Utils.getDomain(r.urls.webVault) === domain);
let environment: Environment;
if (region) {
environment = new WebCloudEnvironment(region, urls);
} else {
environment = new SelfHostedEnvironment(urls);
}
// Override the environment observable with a replay subject
const subject = new ReplaySubject<Environment>(1);
subject.next(environment);
this.environment$ = subject.asObservable();
}
// Web cannot set environment
async setEnvironment(region: Region, urls?: Urls): Promise<Urls> {
return;
}
}
class WebCloudEnvironment extends CloudEnvironment {
constructor(config: RegionConfig, urls: Urls) {
super(config);
// We override the urls to avoid CORS issues
this.urls = urls;
}
}

View File

@@ -7063,12 +7063,6 @@
"enforceOnLoginDesc": {
"message": "Require existing members to change their passwords"
},
"usDomain": {
"message": "bitwarden.com"
},
"euDomain": {
"message": "bitwarden.eu"
},
"smProjectDeleteAccessRestricted": {
"message": "You don't have permissions to delete this project",
"description": "The individual description shown to the user when the user doesn't have access to delete a project."

View File

@@ -171,6 +171,7 @@ const plugins = [
PAYPAL_CONFIG: envConfig["paypal"] ?? {},
FLAGS: envConfig["flags"] ?? {},
DEV_FLAGS: NODE_ENV === "development" ? envConfig["devFlags"] : {},
ADDITIONAL_REGIONS: envConfig["additionalRegions"] ?? [],
}),
new AngularWebpackPlugin({
tsConfigPath: "tsconfig.json",