1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 07:13:32 +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

@@ -2386,12 +2386,6 @@
"message": "EU",
"description": "European Union"
},
"usDomain": {
"message": "bitwarden.com"
},
"euDomain": {
"message": "bitwarden.eu"
},
"accessDenied": {
"message": "Access denied. You do not have permission to view this page."
},

View File

@@ -65,7 +65,7 @@ export class AccountSwitcherService {
name: account.name ?? account.email,
email: account.email,
id: id,
server: await this.environmentService.getHost(id),
server: (await this.environmentService.getEnvironment(id))?.getHostname(),
status: account.status,
isActive: id === activeAccount?.id,
avatarColor: await firstValueFrom(

View File

@@ -94,10 +94,6 @@ export class HomeComponent implements OnInit, OnDestroy {
this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } });
}
get selfHostedDomain() {
return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null;
}
setFormValues() {
this.loginService.setEmail(this.formGroup.value.email);
this.loginService.setRememberEmail(this.formGroup.value.rememberEmail);

View File

@@ -1,6 +1,7 @@
import { Component, NgZone } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
@@ -114,7 +115,8 @@ export class LoginComponent extends BaseLoginComponent {
await this.ssoLoginService.setCodeVerifier(codeVerifier);
await this.ssoLoginService.setSsoState(state);
let url = this.environmentService.getWebVaultUrl();
const env = await firstValueFrom(this.environmentService.environment$);
let url = env.getWebVaultUrl();
if (url == null) {
url = "https://vault.bitwarden.com";
}

View File

@@ -1,4 +1,5 @@
import { Component, Inject } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Router } from "@angular/router";
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
@@ -64,9 +65,9 @@ export class SsoComponent extends BaseSsoComponent {
configService,
);
const url = this.environmentService.getWebVaultUrl();
this.redirectUri = url + "/sso-connector.html";
environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => {
this.redirectUri = env.getWebVaultUrl() + "/sso-connector.html";
});
this.clientId = "browser";
super.onSuccessfulLogin = async () => {

View File

@@ -1,6 +1,6 @@
import { Component, Inject } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { Subject, Subscription } from "rxjs";
import { Subject, Subscription, firstValueFrom } from "rxjs";
import { filter, first, takeUntil } from "rxjs/operators";
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
@@ -225,7 +225,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
}
}
override launchDuoFrameless() {
override async launchDuoFrameless() {
const duoHandOffMessage = {
title: this.i18nService.t("youSuccessfullyLoggedIn"),
message: this.i18nService.t("youMayCloseThisWindow"),
@@ -234,8 +234,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
// we're using the connector here as a way to set a cookie with translations
// before continuing to the duo frameless url
const env = await firstValueFrom(this.environmentService.environment$);
const launchUrl =
this.environmentService.getWebVaultUrl() +
env.getWebVaultUrl() +
"/duo-redirect-connector.html" +
"?duoFramelessUrl=" +
encodeURIComponent(this.duoFramelessUrl) +

View File

@@ -113,7 +113,7 @@ type NotificationBackgroundExtensionMessageHandlers = {
bgGetEnableChangedPasswordPrompt: () => Promise<boolean>;
bgGetEnableAddedLoginPrompt: () => Promise<boolean>;
bgGetExcludedDomains: () => Promise<NeverDomains>;
getWebVaultUrlForNotification: () => string;
getWebVaultUrlForNotification: () => Promise<string>;
};
export {

View File

@@ -1,13 +1,14 @@
import { mock } from "jest-mock-extended";
import { firstValueFrom } from "rxjs";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
@@ -1348,16 +1349,21 @@ describe("NotificationBackground", () => {
const message: NotificationBackgroundExtensionMessage = {
command: "getWebVaultUrlForNotification",
};
const webVaultUrl = "https://example.com";
const env = new SelfHostedEnvironment({ webVault: "https://example.com" });
Object.defineProperty(environmentService, "environment$", {
configurable: true,
get: () => null,
});
const environmentServiceSpy = jest
.spyOn(environmentService, "getWebVaultUrl")
.mockReturnValueOnce(webVaultUrl);
.spyOn(environmentService as any, "environment$", "get")
.mockReturnValue(new BehaviorSubject(env).asObservable());
sendExtensionRuntimeMessage(message);
await flushPromises();
expect(environmentServiceSpy).toHaveBeenCalled();
expect(environmentServiceSpy).toHaveReturnedWith(webVaultUrl);
});
});
});

View File

@@ -165,6 +165,7 @@ export default class NotificationBackground {
notificationQueueMessage: NotificationQueueMessageItem,
) {
const notificationType = notificationQueueMessage.type;
const typeData: Record<string, any> = {
isVaultLocked: notificationQueueMessage.wasVaultLocked,
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
@@ -655,8 +656,9 @@ export default class NotificationBackground {
return await firstValueFrom(this.folderService.folderViews$);
}
private getWebVaultUrl(): string {
return this.environmentService.getWebVaultUrl();
private async getWebVaultUrl(): Promise<string> {
const env = await firstValueFrom(this.environmentService.environment$);
return env.getWebVaultUrl();
}
private async removeIndividualVault(): Promise<boolean> {

View File

@@ -1,5 +1,5 @@
import { mock, mockReset } from "jest-mock-extended";
import { of } from "rxjs";
import { BehaviorSubject, of } from "rxjs";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
@@ -12,9 +12,13 @@ import {
DefaultDomainSettingsService,
DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service";
import {
EnvironmentService,
Region,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { CloudEnvironment } from "@bitwarden/common/platform/services/default-environment.service";
import { I18nService } from "@bitwarden/common/platform/services/i18n.service";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import {
@@ -48,8 +52,6 @@ import {
import OverlayBackground from "./overlay.background";
const iconServerUrl = "https://icons.bitwarden.com/";
describe("OverlayBackground", () => {
const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
@@ -61,9 +63,15 @@ describe("OverlayBackground", () => {
const cipherService = mock<CipherService>();
const autofillService = mock<AutofillService>();
const authService = mock<AuthService>();
const environmentService = mock<EnvironmentService>({
getIconsUrl: () => iconServerUrl,
});
const environmentService = mock<EnvironmentService>();
environmentService.environment$ = new BehaviorSubject(
new CloudEnvironment({
key: Region.US,
domain: "bitwarden.com",
urls: { icons: "https://icons.bitwarden.com/" },
}),
);
const stateService = mock<BrowserStateService>();
const autofillSettingsService = mock<AutofillSettingsService>();
const i18nService = mock<I18nService>();

View File

@@ -53,7 +53,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
private overlayListPort: chrome.runtime.Port;
private focusedFieldData: FocusedFieldData;
private overlayPageTranslations: Record<string, string>;
private readonly iconsServerUrl: string;
private iconsServerUrl: string;
private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = {
openAutofillOverlay: () => this.openOverlay(false),
autofillOverlayElementClosed: ({ message }) => this.overlayElementClosed(message),
@@ -98,9 +98,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private themeStateService: ThemeStateService,
) {
this.iconsServerUrl = this.environmentService.getIconsUrl();
}
) {}
/**
* Removes cached page details for a tab
@@ -118,6 +116,8 @@ class OverlayBackground implements OverlayBackgroundInterface {
*/
async init() {
this.setupExtensionMessageListeners();
const env = await firstValueFrom(this.environmentService.environment$);
this.iconsServerUrl = env.getIconsUrl();
await this.getOverlayVisibility();
await this.getAuthStatus();
}

View File

@@ -613,6 +613,7 @@ export default class MainBackground {
this.authService,
this.environmentService,
this.logService,
this.stateProvider,
true,
);
@@ -1032,10 +1033,6 @@ export default class MainBackground {
return new Promise<void>((resolve) => {
setTimeout(async () => {
await this.environmentService.setUrlsFromStorage();
// Workaround to ignore stateService.activeAccount until URLs are set
// TODO: Remove this when implementing ticket PM-2637
this.environmentService.initialized = true;
if (!this.isPrivateMode) {
await this.refreshBadge();
}

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
@@ -220,7 +222,8 @@ export default class RuntimeBackground {
}
break;
case "authResult": {
const vaultUrl = this.environmentService.getWebVaultUrl();
const env = await firstValueFrom(this.environmentService.environment$);
const vaultUrl = env.getWebVaultUrl();
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
return;
@@ -241,7 +244,8 @@ export default class RuntimeBackground {
break;
}
case "webAuthnResult": {
const vaultUrl = this.environmentService.getWebVaultUrl();
const env = await firstValueFrom(this.environmentService.environment$);
const vaultUrl = env.getWebVaultUrl();
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
return;
@@ -364,7 +368,8 @@ export default class RuntimeBackground {
async sendBwInstalledMessageToVault() {
try {
const vaultUrl = this.environmentService.getWebVaultUrl();
const env = await firstValueFrom(this.environmentService.environment$);
const vaultUrl = env.getWebVaultUrl();
const urlObj = new URL(vaultUrl);
const tabs = await BrowserApi.tabsQuery({ url: `${urlObj.href}*` });

View File

@@ -13,6 +13,7 @@ import {
} from "./environment-service.factory";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
import { stateProviderFactory } from "./state-provider.factory";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
type ConfigServiceFactoryOptions = FactoryOptions & {
@@ -43,6 +44,7 @@ export function configServiceFactory(
await authServiceFactory(cache, opts),
await environmentServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
await stateProviderFactory(cache, opts),
opts.configServiceOptions?.subscribe ?? true,
),
);

View File

@@ -7,6 +7,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ConfigService } from "@bitwarden/common/platform/services/config/config.service";
import { StateProvider } from "@bitwarden/common/platform/state";
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
@@ -21,8 +22,17 @@ export class BrowserConfigService extends ConfigService {
authService: AuthService,
environmentService: EnvironmentService,
logService: LogService,
stateProvider: StateProvider,
subscribe = false,
) {
super(stateService, configApiService, authService, environmentService, logService, subscribe);
super(
stateService,
configApiService,
authService,
environmentService,
logService,
stateProvider,
subscribe,
);
}
}

View File

@@ -1,12 +1,15 @@
import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Region } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
import { StateProvider } from "@bitwarden/common/platform/state";
import { GroupPolicyEnvironment } from "../../admin-console/types/group-policy-environment";
import { devFlagEnabled, devFlagValue } from "../flags";
export class BrowserEnvironmentService extends EnvironmentService {
export class BrowserEnvironmentService extends DefaultEnvironmentService {
constructor(
private logService: LogService,
stateProvider: StateProvider,
@@ -29,16 +32,18 @@ export class BrowserEnvironmentService extends EnvironmentService {
return false;
}
const env = await this.getManagedEnvironment();
const managedEnv = await this.getManagedEnvironment();
const env = await firstValueFrom(this.environment$);
const urls = env.getUrls();
return (
env.base != this.baseUrl ||
env.webVault != this.webVaultUrl ||
env.api != this.webVaultUrl ||
env.identity != this.identityUrl ||
env.icons != this.iconsUrl ||
env.notifications != this.notificationsUrl ||
env.events != this.eventsUrl
managedEnv.base != urls.base ||
managedEnv.webVault != urls.webVault ||
managedEnv.api != urls.api ||
managedEnv.identity != urls.identity ||
managedEnv.icons != urls.icons ||
managedEnv.notifications != urls.notifications ||
managedEnv.events != urls.events
);
}
@@ -62,7 +67,7 @@ export class BrowserEnvironmentService extends EnvironmentService {
async setUrlsToManagedEnvironment() {
const env = await this.getManagedEnvironment();
await this.setUrls({
await this.setEnvironment(Region.SelfHosted, {
base: env.base,
webVault: env.webVault,
api: env.api,

View File

@@ -222,12 +222,12 @@ function getBgService<T>(service: keyof MainBackground) {
},
{
provide: BrowserEnvironmentService,
useExisting: EnvironmentService,
useClass: BrowserEnvironmentService,
deps: [LogService, StateProvider, AccountServiceAbstraction],
},
{
provide: EnvironmentService,
useFactory: getBgService<EnvironmentService>("environmentService"),
deps: [],
useExisting: BrowserEnvironmentService,
},
{ provide: TotpService, useFactory: getBgService<TotpService>("totpService"), deps: [] },
{
@@ -480,6 +480,7 @@ function getBgService<T>(service: keyof MainBackground) {
ConfigApiServiceAbstraction,
AuthServiceAbstraction,
EnvironmentService,
StateProvider,
LogService,
],
},

View File

@@ -6,33 +6,33 @@
<div bitDialogContent>
<p>&copy; Bitwarden Inc. 2015-{{ year }}</p>
<p>{{ "version" | i18n }}: {{ version }}</p>
<ng-container *ngIf="serverConfig$ | async as serverConfig">
<p *ngIf="isCloud">
{{ "serverVersion" | i18n }}: {{ this.serverConfig?.version }}
<span *ngIf="!serverConfig.isValid()">
({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }})
<ng-container *ngIf="data$ | async as data">
<p *ngIf="data.isCloud">
{{ "serverVersion" | i18n }}: {{ data.serverConfig?.version }}
<span *ngIf="!data.serverConfig.isValid()">
({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})
</span>
</p>
<ng-container *ngIf="!isCloud">
<ng-container *ngIf="serverConfig.server">
<ng-container *ngIf="!data.isCloud">
<ng-container *ngIf="data.serverConfig.server">
<p>
{{ "serverVersion" | i18n }} <small>({{ "thirdParty" | i18n }})</small>:
{{ this.serverConfig?.version }}
<span *ngIf="!serverConfig.isValid()">
({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }})
{{ data.serverConfig?.version }}
<span *ngIf="!data.serverConfig.isValid()">
({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})
</span>
</p>
<div>
<small>{{ "thirdPartyServerMessage" | i18n: serverConfig.server?.name }}</small>
<small>{{ "thirdPartyServerMessage" | i18n: data.serverConfig.server?.name }}</small>
</div>
</ng-container>
<p *ngIf="!serverConfig.server">
<p *ngIf="!data.serverConfig.server">
{{ "serverVersion" | i18n }} <small>({{ "selfHostedServer" | i18n }})</small>:
{{ this.serverConfig?.version }}
<span *ngIf="!serverConfig.isValid()">
({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }})
{{ data.serverConfig?.version }}
<span *ngIf="!data.serverConfig.isValid()">
({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})
</span>
</p>
</ng-container>

View File

@@ -1,10 +1,9 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { Observable } from "rxjs";
import { combineLatest, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { ButtonModule, DialogModule } from "@bitwarden/components";
@@ -16,11 +15,13 @@ import { BrowserApi } from "../../platform/browser/browser-api";
imports: [CommonModule, JslibModule, DialogModule, ButtonModule],
})
export class AboutComponent {
protected serverConfig$: Observable<ServerConfig> = this.configService.serverConfig$;
protected year = new Date().getFullYear();
protected version = BrowserApi.getApplicationVersion();
protected isCloud = this.environmentService.isCloud();
protected data$ = combineLatest([
this.configService.serverConfig$,
this.environmentService.environment$.pipe(map((env) => env.isCloud())),
]).pipe(map(([serverConfig, isCloud]) => ({ serverConfig, isCloud })));
constructor(
private configService: ConfigServiceAbstraction,

View File

@@ -446,9 +446,8 @@ export class SettingsComponent implements OnInit {
type: "info",
});
if (confirmed) {
// 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
BrowserApi.createNewTab(this.environmentService.getWebVaultUrl());
const env = await firstValueFrom(this.environmentService.environment$);
await BrowserApi.createNewTab(env.getWebVaultUrl());
}
}
@@ -479,10 +478,9 @@ export class SettingsComponent implements OnInit {
}
async webVault() {
const url = this.environmentService.getWebVaultUrl();
// 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
BrowserApi.createNewTab(url);
const env = await firstValueFrom(this.environmentService.environment$);
const url = env.getWebVaultUrl();
await BrowserApi.createNewTab(url);
}
async import() {

View File

@@ -690,6 +690,8 @@ export class LoginCommand {
codeChallenge: string,
state: string,
): Promise<{ ssoCode: string; orgIdentifier: string }> {
const env = await firstValueFrom(this.environmentService.environment$);
return new Promise((resolve, reject) => {
const callbackServer = http.createServer((req, res) => {
const urlString = "http://localhost" + req.url;
@@ -724,7 +726,7 @@ export class LoginCommand {
}
});
let foundPort = false;
const webUrl = this.environmentService.getWebVaultUrl();
const webUrl = env.getWebVaultUrl();
for (let port = 8065; port <= 8070; port++) {
try {
this.ssoRedirectUri = "http://localhost:" + port;

View File

@@ -47,6 +47,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import {
BiometricStateService,
@@ -62,7 +63,7 @@ import { ConfigApiService } from "@bitwarden/common/platform/services/config/con
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
@@ -312,7 +313,10 @@ export class Main {
this.derivedStateProvider,
);
this.environmentService = new EnvironmentService(this.stateProvider, this.accountService);
this.environmentService = new DefaultEnvironmentService(
this.stateProvider,
this.accountService,
);
this.tokenService = new TokenService(
this.singleUserStateProvider,
@@ -504,6 +508,7 @@ export class Main {
this.authService,
this.environmentService,
this.logService,
this.stateProvider,
true,
);
@@ -703,7 +708,6 @@ export class Main {
await this.storageService.init();
await this.stateService.init();
this.containerService.attachToGlobal(global);
await this.environmentService.setUrlsFromStorage();
await this.i18nService.init();
this.twoFactorService.init();
this.configService.init();

View File

@@ -1,6 +1,10 @@
import { OptionValues } from "commander";
import { firstValueFrom } from "rxjs";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import {
EnvironmentService,
Region,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { Response } from "../models/response";
import { MessageResponse } from "../models/response/message.response";
@@ -29,16 +33,15 @@ export class ConfigCommand {
!options.notifications &&
!options.events
) {
const env = await firstValueFrom(this.environmentService.environment$);
const stringRes = new StringResponse(
this.environmentService.hasBaseUrl()
? this.environmentService.getUrls().base
: "https://bitwarden.com",
env.hasBaseUrl() ? env.getUrls().base : "https://bitwarden.com",
);
return Response.success(stringRes);
}
url = url === "null" || url === "bitwarden.com" || url === "https://bitwarden.com" ? null : url;
await this.environmentService.setUrls({
await this.environmentService.setEnvironment(Region.SelfHosted, {
base: url,
webVault: options.webVault || null,
api: options.api || null,

View File

@@ -1,8 +1,12 @@
import * as inquirer from "inquirer";
import { firstValueFrom } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import {
EnvironmentService,
Region,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { Response } from "../models/response";
@@ -67,9 +71,10 @@ export class ConvertToKeyConnectorCommand {
await this.keyConnectorService.setUsesKeyConnector(true);
// Update environment URL - required for api key login
const urls = this.environmentService.getUrls();
const env = await firstValueFrom(this.environmentService.environment$);
const urls = env.getUrls();
urls.keyConnector = organization.keyConnectorUrl;
await this.environmentService.setUrls(urls);
await this.environmentService.setEnvironment(Region.SelfHosted, urls);
return Response.success();
} else if (answer.convert === "leave") {

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@@ -17,7 +19,7 @@ export class StatusCommand {
async run(): Promise<Response> {
try {
const baseUrl = this.baseUrl();
const baseUrl = await this.baseUrl();
const status = await this.status();
const lastSync = await this.syncService.getLastSync();
const userId = await this.stateService.getUserId();
@@ -37,8 +39,9 @@ export class StatusCommand {
}
}
private baseUrl(): string {
return this.envService.getUrls().base;
private async baseUrl(): Promise<string> {
const env = await firstValueFrom(this.envService.environment$);
return env.getUrls().base;
}
private async status(): Promise<"unauthenticated" | "locked" | "unlocked"> {

View File

@@ -127,7 +127,8 @@ export class SendCreateCommand {
await this.sendApiService.save([encSend, fileData]);
const newSend = await this.sendService.getFromState(encSend.id);
const decSend = await newSend.decrypt();
const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl());
const env = await firstValueFrom(this.environmentService.environment$);
const res = new SendResponse(decSend, env.getWebVaultUrl());
return Response.success(res);
} catch (e) {
return Response.error(e);

View File

@@ -1,4 +1,5 @@
import { OptionValues } from "commander";
import { firstValueFrom } from "rxjs";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -32,7 +33,8 @@ export class SendGetCommand extends DownloadCommand {
return Response.notFound();
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
const env = await firstValueFrom(this.environmentService.environment$);
const webVaultUrl = env.getWebVaultUrl();
let filter = (s: SendView) => true;
let selector = async (s: SendView): Promise<Response> =>
Response.success(new SendResponse(s, webVaultUrl));

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
@@ -21,7 +23,8 @@ export class SendListCommand {
sends = this.searchService.searchSends(sends, normalizedOptions.search);
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
const env = await firstValueFrom(this.environmentService.environment$);
const webVaultUrl = env.getWebVaultUrl();
const res = new ListResponse(sends.map((s) => new SendResponse(s, webVaultUrl)));
return Response.success(res);
}

View File

@@ -1,5 +1,6 @@
import { OptionValues } from "commander";
import * as inquirer from "inquirer";
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@@ -46,7 +47,7 @@ export class SendReceiveCommand extends DownloadCommand {
return Response.badRequest("Failed to parse the provided Send url");
}
const apiUrl = this.getApiUrl(urlObject);
const apiUrl = await this.getApiUrl(urlObject);
const [id, key] = this.getIdAndKey(urlObject);
if (Utils.isNullOrWhitespace(id) || Utils.isNullOrWhitespace(key)) {
@@ -108,8 +109,9 @@ export class SendReceiveCommand extends DownloadCommand {
return [result[0], result[1]];
}
private getApiUrl(url: URL) {
const urls = this.environmentService.getUrls();
private async getApiUrl(url: URL) {
const env = await firstValueFrom(this.environmentService.environment$);
const urls = env.getUrls();
if (url.origin === "https://send.bitwarden.com") {
return "https://api.bitwarden.com";
} else if (url.origin === urls.api) {

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { SendService } from "@bitwarden/common/tools/send/services//send.service.abstraction";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
@@ -18,7 +20,8 @@ export class SendRemovePasswordCommand {
const updatedSend = await this.sendService.get(id);
const decSend = await updatedSend.decrypt();
const webVaultUrl = this.environmentService.getWebVaultUrl();
const env = await firstValueFrom(this.environmentService.environment$);
const webVaultUrl = env.getWebVaultUrl();
const res = new SendResponse(decSend, webVaultUrl);
return Response.success(res);
} catch (e) {

View File

@@ -105,7 +105,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()),
email: await this.tokenService.getEmail(),
avatarColor: await firstValueFrom(this.avatarService.avatarColor$),
server: await this.environmentService.getHost(),
server: (await this.environmentService.getEnvironment())?.getHostname(),
};
} catch {
this.activeAccount = undefined;
@@ -158,7 +158,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
email: baseAccounts[userId].profile.email,
authenticationStatus: await this.authService.getAuthStatus(userId),
avatarColor: await firstValueFrom(this.avatarService.getUserAvatarColor$(userId as UserId)),
server: await this.environmentService.getHost(userId),
server: (await this.environmentService.getEnvironment(userId))?.getHostname(),
};
}

View File

@@ -8,7 +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 } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
@@ -25,7 +24,6 @@ import { NativeMessagingService } from "../../services/native-messaging.service"
export class InitService {
constructor(
@Inject(WINDOW) private win: Window,
private environmentService: EnvironmentServiceAbstraction,
private syncService: SyncServiceAbstraction,
private vaultTimeoutService: VaultTimeoutService,
private i18nService: I18nServiceAbstraction,
@@ -46,10 +44,6 @@ export class InitService {
return async () => {
this.nativeMessagingService.init();
await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process
await this.environmentService.setUrlsFromStorage();
// Workaround to ignore stateService.activeAccount until URLs are set
// TODO: Remove this when implementing ticket PM-2637
this.environmentService.initialized = true;
// 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
this.syncService.fullSync(true);

View File

@@ -49,10 +49,6 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
return this.formGroup.value.email;
}
get selfHostedDomain() {
return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null;
}
constructor(
devicesApiService: DevicesApiServiceAbstraction,
appIdService: AppIdService,
@@ -152,9 +148,6 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
// eslint-disable-next-line rxjs/no-async-subscribe
childComponent.onSaved.pipe(takeUntil(this.componentDestroyed$)).subscribe(async () => {
modal.close();
// 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
this.environmentSelector.updateEnvironmentInfo();
await this.getLoginWithDevice(this.loggedEmail);
});
}

View File

@@ -1,5 +1,6 @@
import { Component, Inject, NgZone, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
@@ -141,7 +142,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
}
}
override launchDuoFrameless() {
override async launchDuoFrameless() {
const duoHandOffMessage = {
title: this.i18nService.t("youSuccessfullyLoggedIn"),
message: this.i18nService.t("youMayCloseThisWindow"),
@@ -150,8 +151,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
// we're using the connector here as a way to set a cookie with translations
// before continuing to the duo frameless url
const env = await firstValueFrom(this.environmentService.environment$);
const launchUrl =
this.environmentService.getWebVaultUrl() +
env.getWebVaultUrl() +
"/duo-redirect-connector.html" +
"?duoFramelessUrl=" +
encodeURIComponent(this.duoFramelessUrl) +

View File

@@ -2346,12 +2346,6 @@
"loggingInOn": {
"message": "Logging in on"
},
"usDomain": {
"message": "bitwarden.com"
},
"euDomain": {
"message": "bitwarden.eu"
},
"selfHostedServer": {
"message": "self-hosted"
},

View File

@@ -10,7 +10,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
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";
@@ -54,7 +54,7 @@ export class Main {
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
messagingService: ElectronMainMessagingService;
stateService: StateService;
environmentService: EnvironmentService;
environmentService: DefaultEnvironmentService;
mainCryptoFunctionService: MainCryptoFunctionService;
desktopCredentialStorageListener: DesktopCredentialStorageListener;
migrationRunner: MigrationRunner;
@@ -148,7 +148,7 @@ export class Main {
new DefaultDerivedStateProvider(this.memoryStorageForStateProviders),
);
this.environmentService = new EnvironmentService(stateProvider, accountService);
this.environmentService = new DefaultEnvironmentService(stateProvider, accountService);
this.tokenService = new TokenService(
singleUserStateProvider,

View File

@@ -1,4 +1,5 @@
import { app, Menu } from "electron";
import { firstValueFrom } from "rxjs";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -45,7 +46,8 @@ export class MenuMain {
}
private async getWebVaultUrl() {
return this.environmentService.getWebVaultUrl() ?? cloudWebVaultUrl;
const env = await firstValueFrom(this.environmentService.environment$);
return env.getWebVaultUrl() ?? cloudWebVaultUrl;
}
private initContextMenu() {

View File

@@ -177,6 +177,7 @@ const renderer = {
ENV: ENV,
FLAGS: envConfig.flags,
DEV_FLAGS: NODE_ENV === "development" ? envConfig.devFlags : {},
ADDITIONAL_REGIONS: envConfig.additionalRegions ?? [],
}),
],
};

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",