diff --git a/angular/src/components/captchaProtected.component.ts b/angular/src/components/captchaProtected.component.ts index b14838650e7..f2c92d0f2e9 100644 --- a/angular/src/components/captchaProtected.component.ts +++ b/angular/src/components/captchaProtected.component.ts @@ -18,10 +18,7 @@ export abstract class CaptchaProtectedComponent { protected platformUtilsService: PlatformUtilsService) { } async setupCaptcha() { - let webVaultUrl = this.environmentService.getWebVaultUrl(); - if (webVaultUrl == null) { - webVaultUrl = 'https://vault.bitwarden.com'; - } + const webVaultUrl = this.environmentService.getWebVaultUrl(); this.captcha = new CaptchaIFrame(window, webVaultUrl, this.i18nService, (token: string) => { diff --git a/angular/src/components/environment.component.ts b/angular/src/components/environment.component.ts index 7809b0fcac0..c36d00b73af 100644 --- a/angular/src/components/environment.component.ts +++ b/angular/src/components/environment.component.ts @@ -6,6 +6,7 @@ import { import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; +import { NotificationsService } from 'jslib-common/abstractions/notifications.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; @Directive() @@ -23,13 +24,16 @@ export class EnvironmentComponent { constructor(protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService, protected i18nService: I18nService) { - this.baseUrl = environmentService.baseUrl || ''; - this.webVaultUrl = environmentService.webVaultUrl || ''; - this.apiUrl = environmentService.apiUrl || ''; - this.identityUrl = environmentService.identityUrl || ''; - this.iconsUrl = environmentService.iconsUrl || ''; - this.notificationsUrl = environmentService.notificationsUrl || ''; - this.enterpriseUrl = environmentService.enterpriseUrl || ''; + + const urls = this.environmentService.getUrls(); + + this.baseUrl = urls.base || ''; + this.webVaultUrl = urls.webVault || ''; + this.apiUrl = urls.api || ''; + this.identityUrl = urls.identity || ''; + this.iconsUrl = urls.icons || ''; + this.notificationsUrl = urls.notifications || ''; + this.enterpriseUrl = urls.enterprise || ''; } async submit() { diff --git a/angular/src/components/icon.component.ts b/angular/src/components/icon.component.ts index 1f3c77eda88..6c82f9c278b 100644 --- a/angular/src/components/icon.component.ts +++ b/angular/src/components/icon.component.ts @@ -38,14 +38,7 @@ export class IconComponent implements OnChanges { private iconsUrl: string; constructor(environmentService: EnvironmentService, protected stateService: StateService) { - this.iconsUrl = environmentService.iconsUrl; - if (!this.iconsUrl) { - if (environmentService.baseUrl) { - this.iconsUrl = environmentService.baseUrl + '/icons'; - } else { - this.iconsUrl = 'https://icons.bitwarden.net'; - } - } + this.iconsUrl = environmentService.getIconsUrl(); } async ngOnChanges() { diff --git a/angular/src/components/lock.component.ts b/angular/src/components/lock.component.ts index 6dd8ddc2a2c..9802244ab2a 100644 --- a/angular/src/components/lock.component.ts +++ b/angular/src/components/lock.component.ts @@ -57,10 +57,9 @@ export class LockComponent implements OnInit { (await this.cryptoService.hasKeyStored('biometric') || !this.platformUtilsService.supportsSecureStorage()); this.biometricText = await this.storageService.get(ConstantsService.biometricText); this.email = await this.userService.getEmail(); - let vaultUrl = this.environmentService.getWebVaultUrl(); - if (vaultUrl == null) { - vaultUrl = 'https://bitwarden.com'; - } + + const webVaultUrl = this.environmentService.getWebVaultUrl(); + const vaultUrl = webVaultUrl === 'https://vault.bitwarden.com' ? 'https://bitwarden.com' : webVaultUrl; this.webVaultHostname = Utils.getHostname(vaultUrl); } diff --git a/angular/src/components/login.component.ts b/angular/src/components/login.component.ts index 7bae8b2b8b8..a77eb724ac5 100644 --- a/angular/src/components/login.component.ts +++ b/angular/src/components/login.component.ts @@ -143,8 +143,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit await this.storageService.save(ConstantsService.ssoCodeVerifierKey, ssoCodeVerifier); // Build URI - const webUrl = this.environmentService.getWebVaultUrl() == null ? 'https://vault.bitwarden.com' : - this.environmentService.getWebVaultUrl(); + const webUrl = this.environmentService.getWebVaultUrl(); // Launch browser this.platformUtilsService.launchUri(webUrl + '/#/sso?clientId=' + clientId + diff --git a/angular/src/components/send/add-edit.component.ts b/angular/src/components/send/add-edit.component.ts index a587a2df9ad..0c3832d86e7 100644 --- a/angular/src/components/send/add-edit.component.ts +++ b/angular/src/components/send/add-edit.component.ts @@ -63,12 +63,7 @@ export class AddEditComponent implements OnInit { { name: i18nService.t('sendTypeFile'), value: SendType.File }, { name: i18nService.t('sendTypeText'), value: SendType.Text }, ]; - const webVaultUrl = this.environmentService.getWebVaultUrl(); - if (webVaultUrl == null) { - this.sendLinkBaseUrl = 'https://send.bitwarden.com/#'; - } else { - this.sendLinkBaseUrl = webVaultUrl + '/#/send/'; - } + this.sendLinkBaseUrl = this.environmentService.getSendUrl(); } get link(): string { diff --git a/angular/src/components/send/send.component.ts b/angular/src/components/send/send.component.ts index 538179bbe4d..577fe7056f1 100644 --- a/angular/src/components/send/send.component.ts +++ b/angular/src/components/send/send.component.ts @@ -170,11 +170,7 @@ export class SendComponent implements OnInit { } copy(s: SendView) { - let sendLinkBaseUrl = 'https://send.bitwarden.com/#'; - const webVaultUrl = this.environmentService.getWebVaultUrl(); - if (webVaultUrl != null) { - sendLinkBaseUrl = webVaultUrl + '/#/send/'; - } + const sendLinkBaseUrl = this.environmentService.getSendUrl(); const link = sendLinkBaseUrl + s.accessId + '/' + s.urlB64Key; this.platformUtilsService.copyToClipboard(link); this.platformUtilsService.showToast('success', null, diff --git a/angular/src/components/sso.component.ts b/angular/src/components/sso.component.ts index d4512a1bcbe..3f20f0c1427 100644 --- a/angular/src/components/sso.component.ts +++ b/angular/src/components/sso.component.ts @@ -7,6 +7,7 @@ import { import { ApiService } from 'jslib-common/abstractions/api.service'; import { AuthService } from 'jslib-common/abstractions/auth.service'; import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; +import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; @@ -43,7 +44,7 @@ export class SsoComponent { protected i18nService: I18nService, protected route: ActivatedRoute, protected storageService: StorageService, protected stateService: StateService, protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, - protected cryptoFunctionService: CryptoFunctionService, + protected cryptoFunctionService: CryptoFunctionService, protected environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService) { } async ngOnInit() { @@ -119,7 +120,7 @@ export class SsoComponent { // Save state (regardless of new or existing) await this.storageService.save(ConstantsService.ssoStateKey, state); - let authorizeUrl = this.apiService.identityBaseUrl + '/connect/authorize?' + + let authorizeUrl = this.environmentService.getIdentityUrl() + '/connect/authorize?' + 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + 'response_type=code&scope=api offline_access&' + 'state=' + state + '&code_challenge=' + codeChallenge + '&' + diff --git a/angular/src/components/two-factor.component.ts b/angular/src/components/two-factor.component.ts index 4e1d538d4dd..e324c586d61 100644 --- a/angular/src/components/two-factor.component.ts +++ b/angular/src/components/two-factor.component.ts @@ -76,10 +76,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy { } if (this.win != null && this.webAuthnSupported) { - let webVaultUrl = this.environmentService.getWebVaultUrl(); - if (webVaultUrl == null) { - webVaultUrl = 'https://vault.bitwarden.com'; - } + const webVaultUrl = this.environmentService.getWebVaultUrl(); this.webAuthn = new WebAuthnIFrame(this.win, webVaultUrl, this.webAuthnNewTab, this.platformUtilsService, this.i18nService, (token: string) => { this.token = token; diff --git a/common/package-lock.json b/common/package-lock.json index 89949a4916c..0741a8ec846 100644 --- a/common/package-lock.json +++ b/common/package-lock.json @@ -16,6 +16,7 @@ "lunr": "^2.3.9", "node-forge": "^0.10.0", "papaparse": "^5.3.0", + "rxjs": "6.6.7", "tldjs": "^2.3.1", "zxcvbn": "^4.4.2" }, @@ -631,6 +632,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -724,6 +736,11 @@ "node": ">=6" } }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1344,6 +1361,14 @@ "glob": "^7.1.3" } }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1409,6 +1434,11 @@ } } }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/common/package.json b/common/package.json index 242a7d70868..ca4cecee8e4 100644 --- a/common/package.json +++ b/common/package.json @@ -37,6 +37,7 @@ "lunr": "^2.3.9", "node-forge": "^0.10.0", "papaparse": "^5.3.0", + "rxjs": "6.6.7", "tldjs": "^2.3.1", "zxcvbn": "^4.4.2" } diff --git a/common/src/abstractions/api.service.ts b/common/src/abstractions/api.service.ts index c5da7a0ff33..f3b8174f9c2 100644 --- a/common/src/abstractions/api.service.ts +++ b/common/src/abstractions/api.service.ts @@ -153,12 +153,6 @@ import { UserKeyResponse } from '../models/response/userKeyResponse'; import { SendAccessView } from '../models/view/sendAccessView'; export abstract class ApiService { - urlsSet: boolean; - apiBaseUrl: string; - identityBaseUrl: string; - eventsBaseUrl: string; - - setUrls: (urls: EnvironmentUrls) => void; postIdentityToken: (request: TokenRequest) => Promise; refreshIdentityToken: () => Promise; diff --git a/common/src/abstractions/environment.service.ts b/common/src/abstractions/environment.service.ts index d68d6a6fc4c..8687201db95 100644 --- a/common/src/abstractions/environment.service.ts +++ b/common/src/abstractions/environment.service.ts @@ -1,14 +1,29 @@ -export abstract class EnvironmentService { - baseUrl: string; - webVaultUrl: string; - apiUrl: string; - identityUrl: string; - iconsUrl: string; - notificationsUrl: string; - eventsUrl: string; - enterpriseUrl: string; +import { Observable } from 'rxjs'; +export type Urls = { + base?: string; + webVault?: string; + api?: string; + identity?: string; + icons?: string; + notifications?: string; + events?: string; + enterprise?: string; +}; + +export abstract class EnvironmentService { + urls: Observable; + + hasBaseUrl: () => boolean; + getNotificationsUrl: () => string; + getEnterpriseUrl: () => string; getWebVaultUrl: () => string; + getSendUrl: () => string; + getIconsUrl: () => string; + getApiUrl: () => string; + getIdentityUrl: () => string; + getEventsUrl: () => string; setUrlsFromStorage: () => Promise; - setUrls: (urls: any) => Promise; + setUrls: (urls: any, saveSettings?: boolean) => Promise; + getUrls: () => Urls; } diff --git a/common/src/abstractions/notifications.service.ts b/common/src/abstractions/notifications.service.ts index 91db76bdd68..3e4c06e4504 100644 --- a/common/src/abstractions/notifications.service.ts +++ b/common/src/abstractions/notifications.service.ts @@ -1,7 +1,5 @@ -import { EnvironmentService } from './environment.service'; - export abstract class NotificationsService { - init: (environmentService: EnvironmentService) => Promise; + init: () => Promise; updateConnection: (sync?: boolean) => Promise; reconnectFromActivity: () => Promise; disconnectFromInactivity: () => Promise; diff --git a/common/src/services/api.service.ts b/common/src/services/api.service.ts index aed2a2578b9..0ac7854886c 100644 --- a/common/src/services/api.service.ts +++ b/common/src/services/api.service.ts @@ -5,8 +5,6 @@ import { ApiService as ApiServiceAbstraction } from '../abstractions/api.service import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { TokenService } from '../abstractions/token.service'; -import { EnvironmentUrls } from '../models/domain/environmentUrls'; - import { AttachmentRequest } from '../models/request/attachmentRequest'; import { BitPayInvoiceRequest } from '../models/request/bitPayInvoiceRequest'; import { CipherBulkDeleteRequest } from '../models/request/cipherBulkDeleteRequest'; @@ -160,23 +158,19 @@ import { ChallengeResponse } from '../models/response/twoFactorWebAuthnResponse' import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse'; import { UserKeyResponse } from '../models/response/userKeyResponse'; +import { EnvironmentService } from '../abstractions'; import { IdentityCaptchaResponse } from '../models/response/identityCaptchaResponse'; import { SendAccessView } from '../models/view/sendAccessView'; export class ApiService implements ApiServiceAbstraction { - urlsSet: boolean = false; - apiBaseUrl: string; - identityBaseUrl: string; - eventsBaseUrl: string; - private device: DeviceType; private deviceType: string; private isWebClient = false; private isDesktopClient = false; - private usingBaseUrl = false; constructor(private tokenService: TokenService, private platformUtilsService: PlatformUtilsService, - private logoutCallback: (expired: boolean) => Promise, private customUserAgent: string = null) { + private environmentService: EnvironmentService, private logoutCallback: (expired: boolean) => Promise, + private customUserAgent: string = null) { this.device = platformUtilsService.getDevice(); this.deviceType = this.device.toString(); this.isWebClient = this.device === DeviceType.IEBrowser || this.device === DeviceType.ChromeBrowser || @@ -187,33 +181,6 @@ export class ApiService implements ApiServiceAbstraction { this.device === DeviceType.LinuxDesktop; } - setUrls(urls: EnvironmentUrls): void { - this.urlsSet = true; - - if (urls.base != null) { - this.usingBaseUrl = true; - this.apiBaseUrl = urls.base + '/api'; - this.identityBaseUrl = urls.base + '/identity'; - this.eventsBaseUrl = urls.base + '/events'; - return; - } - - this.apiBaseUrl = urls.api; - this.identityBaseUrl = urls.identity; - this.eventsBaseUrl = urls.events; - - // Production - if (this.apiBaseUrl == null) { - this.apiBaseUrl = 'https://api.bitwarden.com'; - } - if (this.identityBaseUrl == null) { - this.identityBaseUrl = 'https://identity.bitwarden.com'; - } - if (this.eventsBaseUrl == null) { - this.eventsBaseUrl = 'https://events.bitwarden.com'; - } - } - // Auth APIs async postIdentityToken(request: TokenRequest): Promise { @@ -226,7 +193,7 @@ export class ApiService implements ApiServiceAbstraction { headers.set('User-Agent', this.customUserAgent); } request.alterIdentityTokenHeaders(headers); - const response = await this.fetch(new Request(this.identityBaseUrl + '/connect/token', { + const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + '/connect/token', { body: this.qsStringify(request.toIdentityToken(request.clientId ?? this.platformUtilsService.identityClientId)), credentials: this.getCredentials(), cache: 'no-store', @@ -1399,7 +1366,7 @@ export class ApiService implements ApiServiceAbstraction { if (this.customUserAgent != null) { headers.set('User-Agent', this.customUserAgent); } - const response = await this.fetch(new Request(this.eventsBaseUrl + '/collect', { + const response = await this.fetch(new Request(this.environmentService.getEventsUrl() + '/collect', { cache: 'no-store', credentials: this.getCredentials(), method: 'POST', @@ -1473,7 +1440,7 @@ export class ApiService implements ApiServiceAbstraction { } const path = `/account/prevalidate?domainHint=${encodeURIComponent(identifier)}`; - const response = await this.fetch(new Request(this.identityBaseUrl + path, { + const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + path, { cache: 'no-store', credentials: this.getCredentials(), headers: headers, @@ -1503,7 +1470,7 @@ export class ApiService implements ApiServiceAbstraction { } const decodedToken = this.tokenService.decodeToken(); - const response = await this.fetch(new Request(this.identityBaseUrl + '/connect/token', { + const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + '/connect/token', { body: this.qsStringify({ grant_type: 'refresh_token', client_id: decodedToken.client_id, @@ -1528,7 +1495,7 @@ export class ApiService implements ApiServiceAbstraction { private async send(method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body: any, authed: boolean, hasResponse: boolean, apiUrl?: string, alterHeaders?: (headers: Headers) => void): Promise { - apiUrl = Utils.isNullOrWhitespace(apiUrl) ? this.apiBaseUrl : apiUrl; + apiUrl = Utils.isNullOrWhitespace(apiUrl) ? this.environmentService.getApiUrl() : apiUrl; const headers = new Headers({ 'Device-Type': this.deviceType, }); @@ -1601,7 +1568,7 @@ export class ApiService implements ApiServiceAbstraction { } private getCredentials(): RequestCredentials { - if (!this.isWebClient || this.usingBaseUrl) { + if (!this.isWebClient || this.environmentService.hasBaseUrl()) { return 'include'; } return undefined; diff --git a/common/src/services/environment.service.ts b/common/src/services/environment.service.ts index daf7b5c6b1f..3b4d321d25a 100644 --- a/common/src/services/environment.service.ts +++ b/common/src/services/environment.service.ts @@ -1,32 +1,119 @@ +import { Observable, Subject } from 'rxjs'; + import { EnvironmentUrls } from '../models/domain/environmentUrls'; import { ConstantsService } from './constants.service'; -import { ApiService } from '../abstractions/api.service'; -import { EnvironmentService as EnvironmentServiceAbstraction } from '../abstractions/environment.service'; -import { NotificationsService } from '../abstractions/notifications.service'; +import { EnvironmentService as EnvironmentServiceAbstraction, Urls } from '../abstractions/environment.service'; import { StorageService } from '../abstractions/storage.service'; export class EnvironmentService implements EnvironmentServiceAbstraction { - baseUrl: string; - webVaultUrl: string; - apiUrl: string; - identityUrl: string; - iconsUrl: string; - notificationsUrl: string; - eventsUrl: string; - enterpriseUrl: string; - constructor(private apiService: ApiService, private storageService: StorageService, - private notificationsService: NotificationsService) { } + private readonly urlsSubject = new Subject(); + urls: Observable = this.urlsSubject; // tslint:disable-line - getWebVaultUrl(): string { + private baseUrl: string; + private webVaultUrl: string; + private apiUrl: string; + private identityUrl: string; + private iconsUrl: string; + private notificationsUrl: string; + private eventsUrl: string; + private enterpriseUrl: string; + + constructor(private storageService: StorageService) {} + + hasBaseUrl() { + return this.baseUrl != null; + } + + getNotificationsUrl() { + if (this.notificationsUrl != null) { + return this.notificationsUrl; + } + + if (this.baseUrl != null) { + return this.baseUrl + '/notifications'; + } + + return 'https://notifications.bitwarden.com'; + } + + getEnterpriseUrl() { + if (this.enterpriseUrl != null) { + return this.enterpriseUrl; + } + + if (this.baseUrl != null) { + return this.baseUrl + '/portal'; + } + + return 'https://portal.bitwarden.com'; + } + + getWebVaultUrl() { if (this.webVaultUrl != null) { return this.webVaultUrl; - } else if (this.baseUrl) { + } + + if (this.baseUrl) { return this.baseUrl; } - return null; + return 'https://vault.bitwarden.com'; + } + + getSendUrl() { + return this.getWebVaultUrl() === 'https://vault.bitwarden.com' + ? 'https://send.bitwarden.com/#' + : this.getWebVaultUrl() + '/#/send/'; + } + + getIconsUrl() { + if (this.iconsUrl != null) { + return this.iconsUrl; + } + + if (this.baseUrl) { + return this.baseUrl + '/icons'; + } + + return 'https://icons.bitwarden.net'; + } + + getApiUrl() { + if (this.apiUrl != null) { + return this.apiUrl; + } + + if (this.baseUrl) { + return this.baseUrl + '/api'; + } + + return 'https://api.bitwarden.com'; + } + + getIdentityUrl() { + if (this.identityUrl != null) { + return this.identityUrl; + } + + if (this.baseUrl) { + return this.baseUrl + '/identity'; + } + + return 'https://identity.bitwarden.com'; + } + + getEventsUrl() { + if (this.eventsUrl != null) { + return this.eventsUrl; + } + + if (this.baseUrl) { + return this.baseUrl + '/events'; + } + + return 'https://events.bitwarden.com'; } async setUrlsFromStorage(): Promise { @@ -46,7 +133,6 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { if (urls.base) { this.baseUrl = envUrls.base = urls.base; - this.apiService.setUrls(envUrls); return; } @@ -57,10 +143,9 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { this.notificationsUrl = urls.notifications; this.eventsUrl = envUrls.events = urls.events; this.enterpriseUrl = urls.enterprise; - this.apiService.setUrls(envUrls); } - async setUrls(urls: any): Promise { + async setUrls(urls: Urls, saveSettings: boolean = true): Promise { urls.base = this.formatUrl(urls.base); urls.webVault = this.formatUrl(urls.webVault); urls.api = this.formatUrl(urls.api); @@ -70,16 +155,18 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { urls.events = this.formatUrl(urls.events); urls.enterprise = this.formatUrl(urls.enterprise); - await this.storageService.save(ConstantsService.environmentUrlsKey, { - base: urls.base, - api: urls.api, - identity: urls.identity, - webVault: urls.webVault, - icons: urls.icons, - notifications: urls.notifications, - events: urls.events, - enterprise: urls.enterprise, - }); + if (saveSettings) { + await this.storageService.save(ConstantsService.environmentUrlsKey, { + base: urls.base, + api: urls.api, + identity: urls.identity, + webVault: urls.webVault, + icons: urls.icons, + notifications: urls.notifications, + events: urls.events, + enterprise: urls.enterprise, + }); + } this.baseUrl = urls.base; this.webVaultUrl = urls.webVault; @@ -90,22 +177,24 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { this.eventsUrl = urls.events; this.enterpriseUrl = urls.enterprise; - const envUrls = new EnvironmentUrls(); - if (this.baseUrl) { - envUrls.base = this.baseUrl; - } else { - envUrls.api = this.apiUrl; - envUrls.identity = this.identityUrl; - envUrls.events = this.eventsUrl; - } + this.urlsSubject.next(urls); - this.apiService.setUrls(envUrls); - if (this.notificationsService != null) { - this.notificationsService.init(this); - } return urls; } + getUrls() { + return { + base: this.baseUrl, + webVault: this.webVaultUrl, + api: this.apiUrl, + identity: this.identityUrl, + icons: this.iconsUrl, + notifications: this.notificationsUrl, + events: this.eventsUrl, + enterprise: this.enterpriseUrl, + }; + } + private formatUrl(url: string): string { if (url == null || url === '') { return null; diff --git a/common/src/services/notifications.service.ts b/common/src/services/notifications.service.ts index fd3b9d00ed5..7fb401f9b2c 100644 --- a/common/src/services/notifications.service.ts +++ b/common/src/services/notifications.service.ts @@ -29,18 +29,20 @@ export class NotificationsService implements NotificationsServiceAbstraction { constructor(private userService: UserService, private syncService: SyncService, private appIdService: AppIdService, private apiService: ApiService, - private vaultTimeoutService: VaultTimeoutService, + private vaultTimeoutService: VaultTimeoutService, private environmentService: EnvironmentService, private logoutCallback: () => Promise, private logService: LogService) { + this.environmentService.urls.subscribe(() => { + if (!this.inited) { + return; + } + + this.init(); + }); } - async init(environmentService: EnvironmentService): Promise { + async init(): Promise { this.inited = false; - this.url = 'https://notifications.bitwarden.com'; - if (environmentService.notificationsUrl != null) { - this.url = environmentService.notificationsUrl; - } else if (environmentService.baseUrl != null) { - this.url = environmentService.baseUrl + '/notifications'; - } + this.url = this.environmentService.getNotificationsUrl(); // Set notifications server URL to `https://-` to effectively disable communication // with the notifications server from the client app diff --git a/node/src/cli/commands/login.command.ts b/node/src/cli/commands/login.command.ts index 3183b66b990..ed8d8adad8d 100644 --- a/node/src/cli/commands/login.command.ts +++ b/node/src/cli/commands/login.command.ts @@ -279,10 +279,7 @@ export class LoginCommand { } }); let foundPort = false; - let webUrl = this.environmentService.getWebVaultUrl(); - if (webUrl == null) { - webUrl = 'https://vault.bitwarden.com'; - } + const webUrl = this.environmentService.getWebVaultUrl(); for (let port = 8065; port <= 8070; port++) { try { this.ssoRedirectUri = 'http://localhost:' + port; diff --git a/node/src/services/nodeApi.service.ts b/node/src/services/nodeApi.service.ts index af1152890a7..7b0c283430f 100644 --- a/node/src/services/nodeApi.service.ts +++ b/node/src/services/nodeApi.service.ts @@ -4,6 +4,7 @@ import * as fe from 'node-fetch'; import { ApiService } from 'jslib-common/services/api.service'; +import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { TokenService } from 'jslib-common/abstractions/token.service'; @@ -15,8 +16,9 @@ import { TokenService } from 'jslib-common/abstractions/token.service'; export class NodeApiService extends ApiService { constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, - logoutCallback: (expired: boolean) => Promise, customUserAgent: string = null) { - super(tokenService, platformUtilsService, logoutCallback, customUserAgent); + environmentService: EnvironmentService, logoutCallback: (expired: boolean) => Promise, + customUserAgent: string = null) { + super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent); } nativeFetch(request: Request): Promise {