bitwarden-estensione-browser/apps/browser/src/services/browserPlatformUtils.servic...

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

378 lines
11 KiB
TypeScript
Raw Normal View History

2022-06-14 17:10:53 +02:00
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { ClientType } from "@bitwarden/common/enums/clientType";
import { DeviceType } from "@bitwarden/common/enums/deviceType";
import { ThemeType } from "@bitwarden/common/enums/themeType";
2018-01-09 20:26:20 +01:00
2022-02-24 18:14:04 +01:00
import { BrowserApi } from "../browser/browserApi";
import { SafariApp } from "../browser/safariApp";
[Account Switching] Base changes for account switching (#2250) * Pull in jslib * Create new state models * Create browser specific stateService * Remove registration deprecated services, register stateService * Replace usage of deprecated services (user, constants) * Add missing properties to BrowserGroupingsComponentState * Remove StorageService from initFactory * Clear the correct state * Add null check when restoring send-grouping state * add remember email * Initialize stateservice in services.module * Fix 'lock now' not working * Comment to remove setting defaults on install * Pull jslib * Remove setting defaults on install * Bump jslib * Pass the current userId to services when logging out * Bump jslib * Override vaultTimeout default on account addition * Pull latest jslib * Retrieve vaultTimeout from stateService * Record activity per Account * Add userId to logout and add fallback if not present * Register AccountFactory * Pass userId in messages * Base changes for account switching di fixes (#2280) * [bug] Null checks on Account init * [bug] Use same stateService instance for all operations We override the stateService in browser, but currently don't pull the background service into popup and allow jslib to create its own instance of the base StateService for jslib services. This causes a split in in memory state between the three isntances that results in many errors, namely locking not working. * [chore] Update jslib * Pull in jslib * Pull in jslib * Pull in latest jslib to multiple stateservice inits * Check vault states before executing processReload * Adjust iterator * Update native messaging to include the userId (#2290) * Re-Add UserVerificationService * Fix email not being remembered by base component * Improve readability of reloadProcess * Removed unneeded null check * Fix constructor dependency (stateService) * Added missing await * Simplify dependency registration * Fixed typos * Reverted back to simple loop * Use vaultTimeoutService to retrieve Timeout Co-authored-by: Addison Beck <abeck@bitwarden.com> Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
2022-01-27 22:22:51 +01:00
import { StateService } from "../services/abstractions/state.service";
2018-04-14 15:39:47 +02:00
const DialogPromiseExpiration = 600000; // 10 minutes
2018-04-10 20:20:03 +02:00
2018-01-09 20:26:20 +01:00
export default class BrowserPlatformUtilsService implements PlatformUtilsService {
2018-04-10 20:20:03 +02:00
private showDialogResolves = new Map<number, { resolve: (value: boolean) => void; date: Date }>();
private passwordDialogResolves = new Map<
number,
{ tryResolve: (canceled: boolean, password: string) => Promise<boolean>; date: Date }
>();
2018-01-09 20:26:20 +01:00
private deviceCache: DeviceType = null;
private prefersColorSchemeDark = window.matchMedia("(prefers-color-scheme: dark)");
2021-12-21 15:43:35 +01:00
constructor(
private messagingService: MessagingService,
[Account Switching] Base changes for account switching (#2250) * Pull in jslib * Create new state models * Create browser specific stateService * Remove registration deprecated services, register stateService * Replace usage of deprecated services (user, constants) * Add missing properties to BrowserGroupingsComponentState * Remove StorageService from initFactory * Clear the correct state * Add null check when restoring send-grouping state * add remember email * Initialize stateservice in services.module * Fix 'lock now' not working * Comment to remove setting defaults on install * Pull jslib * Remove setting defaults on install * Bump jslib * Pass the current userId to services when logging out * Bump jslib * Override vaultTimeout default on account addition * Pull latest jslib * Retrieve vaultTimeout from stateService * Record activity per Account * Add userId to logout and add fallback if not present * Register AccountFactory * Pass userId in messages * Base changes for account switching di fixes (#2280) * [bug] Null checks on Account init * [bug] Use same stateService instance for all operations We override the stateService in browser, but currently don't pull the background service into popup and allow jslib to create its own instance of the base StateService for jslib services. This causes a split in in memory state between the three isntances that results in many errors, namely locking not working. * [chore] Update jslib * Pull in jslib * Pull in jslib * Pull in latest jslib to multiple stateservice inits * Check vault states before executing processReload * Adjust iterator * Update native messaging to include the userId (#2290) * Re-Add UserVerificationService * Fix email not being remembered by base component * Improve readability of reloadProcess * Removed unneeded null check * Fix constructor dependency (stateService) * Added missing await * Simplify dependency registration * Fixed typos * Reverted back to simple loop * Use vaultTimeoutService to retrieve Timeout Co-authored-by: Addison Beck <abeck@bitwarden.com> Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
2022-01-27 22:22:51 +01:00
private stateService: StateService,
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
private biometricCallback: () => Promise<boolean>
) {}
2021-12-21 15:43:35 +01:00
2018-01-09 20:26:20 +01:00
getDevice(): DeviceType {
2018-07-09 15:12:41 +02:00
if (this.deviceCache) {
2018-01-05 17:13:24 +01:00
return this.deviceCache;
2021-12-21 15:43:35 +01:00
}
if (
navigator.userAgent.indexOf(" Firefox/") !== -1 ||
navigator.userAgent.indexOf(" Gecko/") !== -1
2021-12-21 15:43:35 +01:00
) {
2018-07-09 15:12:41 +02:00
this.deviceCache = DeviceType.FirefoxExtension;
} else if (
(!!(window as any).opr && !!opr.addons) ||
!!(window as any).opera ||
navigator.userAgent.indexOf(" OPR/") >= 0
2021-12-21 15:43:35 +01:00
) {
2018-07-09 15:12:41 +02:00
this.deviceCache = DeviceType.OperaExtension;
2018-01-05 17:13:24 +01:00
} else if (navigator.userAgent.indexOf(" Edg/") !== -1) {
2018-07-09 15:12:41 +02:00
this.deviceCache = DeviceType.EdgeExtension;
} else if (navigator.userAgent.indexOf(" Vivaldi/") !== -1) {
2018-07-09 15:12:41 +02:00
this.deviceCache = DeviceType.VivaldiExtension;
2018-01-16 03:40:42 +01:00
} else if ((window as any).chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) {
2018-07-09 15:12:41 +02:00
this.deviceCache = DeviceType.ChromeExtension;
} else if (navigator.userAgent.indexOf(" Safari/") !== -1) {
2018-01-05 17:13:24 +01:00
this.deviceCache = DeviceType.SafariExtension;
2021-12-21 15:43:35 +01:00
}
2018-01-05 17:13:24 +01:00
return this.deviceCache;
2021-12-21 15:43:35 +01:00
}
2018-01-05 17:13:24 +01:00
getDeviceString(): string {
2018-07-09 15:30:37 +02:00
const device = DeviceType[this.getDevice()].toLowerCase();
return device.replace("extension", "");
2021-12-21 15:43:35 +01:00
}
2022-02-08 13:22:20 +01:00
getClientType(): ClientType {
return ClientType.Browser;
}
isFirefox(): boolean {
2018-07-09 15:12:41 +02:00
return this.getDevice() === DeviceType.FirefoxExtension;
2021-12-21 15:43:35 +01:00
}
isChrome(): boolean {
2018-07-09 15:12:41 +02:00
return this.getDevice() === DeviceType.ChromeExtension;
2021-12-21 15:43:35 +01:00
}
isEdge(): boolean {
2018-07-09 15:12:41 +02:00
return this.getDevice() === DeviceType.EdgeExtension;
2021-12-21 15:43:35 +01:00
}
isOpera(): boolean {
2018-07-09 15:12:41 +02:00
return this.getDevice() === DeviceType.OperaExtension;
2021-12-21 15:43:35 +01:00
}
isVivaldi(): boolean {
2018-07-09 15:12:41 +02:00
return this.getDevice() === DeviceType.VivaldiExtension;
2021-12-21 15:43:35 +01:00
}
isSafari(): boolean {
2018-07-09 15:12:41 +02:00
return this.getDevice() === DeviceType.SafariExtension;
2021-12-21 15:43:35 +01:00
}
2018-06-08 05:36:58 +02:00
isIE(): boolean {
2019-08-20 19:54:10 +02:00
return false;
2021-12-21 15:43:35 +01:00
}
2018-02-27 05:51:17 +01:00
isMacAppStore(): boolean {
2019-08-20 19:54:10 +02:00
return false;
2021-12-21 15:43:35 +01:00
}
2019-08-20 19:54:10 +02:00
async isViewOpen(): Promise<boolean> {
if (await BrowserApi.isPopupOpen()) {
return true;
2021-12-21 15:43:35 +01:00
}
2018-01-13 03:31:46 +01:00
if (this.isSafari()) {
2019-08-20 19:54:10 +02:00
return false;
2021-12-21 15:43:35 +01:00
}
2018-01-04 22:49:58 +01:00
const sidebarView = this.sidebarViewName();
const sidebarOpen =
sidebarView != null && chrome.extension.getViews({ type: sidebarView }).length > 0;
2018-01-05 17:13:24 +01:00
if (sidebarOpen) {
2019-08-20 19:54:10 +02:00
return true;
2021-12-21 15:43:35 +01:00
}
2018-01-05 17:13:24 +01:00
const tabOpen = chrome.extension.getViews({ type: "tab" }).length > 0;
2019-08-20 19:54:10 +02:00
return tabOpen;
2021-12-21 15:43:35 +01:00
}
2018-06-09 20:50:29 +02:00
lockTimeout(): number {
return null;
2021-12-21 15:43:35 +01:00
}
2018-01-25 20:31:54 +01:00
launchUri(uri: string, options?: any): void {
BrowserApi.createNewTab(uri, options && options.extensionPage === true);
2021-12-21 15:43:35 +01:00
}
2018-01-25 20:31:54 +01:00
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
2021-12-21 15:43:35 +01:00
}
2021-04-07 20:43:07 +02:00
getApplicationVersion(): Promise<string> {
return Promise.resolve(BrowserApi.getApplicationVersion());
2021-12-21 15:43:35 +01:00
}
supportsWebAuthn(win: Window): boolean {
return typeof PublicKeyCredential !== "undefined";
2021-12-21 15:43:35 +01:00
}
2018-05-16 21:30:36 +02:00
supportsDuo(): boolean {
return true;
2021-12-21 15:43:35 +01:00
}
2018-10-03 15:46:00 +02:00
showToast(
type: "error" | "success" | "warning" | "info",
title: string,
text: string | string[],
options?: any
2021-12-21 15:43:35 +01:00
): void {
2018-10-03 05:33:56 +02:00
this.messagingService.send("showToast", {
text: text,
2018-04-10 20:20:03 +02:00
title: title,
type: type,
2018-10-03 05:33:56 +02:00
options: options,
2021-12-21 15:43:35 +01:00
});
}
showDialog(
body: string,
title?: string,
confirmText?: string,
cancelText?: string,
type?: string,
2022-02-24 18:14:04 +01:00
bodyIsHtml = false
2021-12-21 15:43:35 +01:00
) {
2018-04-10 20:20:03 +02:00
const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
this.messagingService.send("showDialog", {
text: bodyIsHtml ? null : body,
html: bodyIsHtml ? body : null,
2018-04-10 20:20:03 +02:00
title: title,
confirmText: confirmText,
cancelText: cancelText,
type: type,
dialogId: dialogId,
2021-12-21 15:43:35 +01:00
});
2021-02-10 16:40:15 +01:00
return new Promise<boolean>((resolve) => {
2018-04-10 20:20:03 +02:00
this.showDialogResolves.set(dialogId, { resolve: resolve, date: new Date() });
2021-12-21 15:43:35 +01:00
});
}
2018-02-13 23:23:30 +01:00
isDev(): boolean {
2018-04-14 19:32:56 +02:00
return process.env.ENV === "development";
2021-12-21 15:43:35 +01:00
}
2018-06-30 19:51:20 +02:00
isSelfHost(): boolean {
return false;
2021-12-21 15:43:35 +01:00
}
2018-02-16 20:00:41 +01:00
copyToClipboard(text: string, options?: any): void {
let win = window;
let doc = window.document;
2018-08-17 18:25:09 +02:00
if (options && (options.window || options.win)) {
win = options.window || options.win;
doc = win.document;
} else if (options && options.doc) {
doc = options.doc;
2021-12-21 15:43:35 +01:00
}
2019-05-30 15:37:09 +02:00
const clearing = options ? !!options.clearing : false;
2019-02-27 15:28:16 +01:00
const clearMs: number = options && options.clearMs ? options.clearMs : null;
2021-12-21 15:43:35 +01:00
if (this.isSafari()) {
SafariApp.sendMessageToApp("copyToClipboard", text).then(() => {
2019-05-30 15:37:09 +02:00
if (!clearing && this.clipboardWriteCallback != null) {
2019-02-27 15:28:16 +01:00
this.clipboardWriteCallback(text, clearMs);
}
2018-07-09 15:12:41 +02:00
});
} else if (
this.isFirefox() &&
(win as any).navigator.clipboard &&
2020-09-15 16:50:45 +02:00
(win as any).navigator.clipboard.writeText
) {
2018-01-16 03:40:42 +01:00
(win as any).navigator.clipboard.writeText(text).then(() => {
if (!clearing && this.clipboardWriteCallback != null) {
this.clipboardWriteCallback(text, clearMs);
}
2018-01-05 17:13:24 +01:00
});
} else if ((win as any).clipboardData && (win as any).clipboardData.setData) {
2018-07-09 15:12:41 +02:00
// IE specific code path to prevent textarea being shown while dialog is visible.
(win as any).clipboardData.setData("Text", text);
if (!clearing && this.clipboardWriteCallback != null) {
2018-07-09 15:12:41 +02:00
this.clipboardWriteCallback(text, clearMs);
2021-12-21 15:43:35 +01:00
}
2018-07-09 15:12:41 +02:00
} else if (doc.queryCommandSupported && doc.queryCommandSupported("copy")) {
if (this.isChrome() && text === "") {
text = "\u0000";
}
2021-12-21 15:43:35 +01:00
2018-07-09 15:12:41 +02:00
const textarea = doc.createElement("textarea");
textarea.textContent = text == null || text === "" ? " " : text;
// Prevent scrolling to bottom of page in MS Edge.
2018-07-09 15:12:41 +02:00
textarea.style.position = "fixed";
2018-02-27 05:51:17 +01:00
doc.body.appendChild(textarea);
2019-08-20 19:54:10 +02:00
textarea.select();
2021-12-21 15:43:35 +01:00
2019-08-20 19:54:10 +02:00
try {
// Security exception may be thrown by some browsers.
2018-01-13 03:31:46 +01:00
if (doc.execCommand("copy") && !clearing && this.clipboardWriteCallback != null) {
this.clipboardWriteCallback(text, clearMs);
2018-01-05 17:13:24 +01:00
}
2019-02-27 04:37:21 +01:00
} catch (e) {
2022-02-24 18:14:04 +01:00
// eslint-disable-next-line
2018-04-23 19:04:11 +02:00
console.warn("Copy to clipboard failed.", e);
2018-01-04 22:49:58 +01:00
} finally {
2019-02-27 04:37:21 +01:00
doc.body.removeChild(textarea);
2021-12-21 15:43:35 +01:00
}
}
}
2019-02-27 04:37:21 +01:00
async readFromClipboard(options?: any): Promise<string> {
let win = window;
let doc = window.document;
2018-01-04 22:49:58 +01:00
if (options && (options.window || options.win)) {
2019-02-27 04:37:21 +01:00
win = options.window || options.win;
doc = win.document;
} else if (options && options.doc) {
doc = options.doc;
2021-12-21 15:43:35 +01:00
}
2018-01-04 22:49:58 +01:00
if (this.isSafari()) {
return await SafariApp.sendMessageToApp("readFromClipboard");
2019-02-27 04:37:21 +01:00
} else if (
2018-01-04 22:49:58 +01:00
this.isFirefox() &&
(win as any).navigator.clipboard &&
(win as any).navigator.clipboard.readText
2021-12-21 15:43:35 +01:00
) {
2019-02-27 04:37:21 +01:00
return await (win as any).navigator.clipboard.readText();
} else if (doc.queryCommandSupported && doc.queryCommandSupported("paste")) {
2018-01-04 22:49:58 +01:00
const textarea = doc.createElement("textarea");
// Prevent scrolling to bottom of page in MS Edge.
textarea.style.position = "fixed";
doc.body.appendChild(textarea);
textarea.focus();
try {
// Security exception may be thrown by some browsers.
2018-01-05 17:13:24 +01:00
if (doc.execCommand("paste")) {
2019-08-20 19:54:10 +02:00
return textarea.value;
2018-01-05 17:13:24 +01:00
}
} catch (e) {
2022-02-24 18:14:04 +01:00
// eslint-disable-next-line
2018-01-25 20:31:54 +01:00
console.warn("Read from clipboard failed.", e);
} finally {
2021-04-07 20:43:07 +02:00
doc.body.removeChild(textarea);
2021-12-21 15:43:35 +01:00
}
}
2021-04-07 20:43:07 +02:00
return null;
2021-12-21 15:43:35 +01:00
}
2021-04-07 20:43:07 +02:00
resolveDialogPromise(dialogId: number, confirmed: boolean) {
2018-10-03 05:33:56 +02:00
if (this.showDialogResolves.has(dialogId)) {
const resolveObj = this.showDialogResolves.get(dialogId);
resolveObj.resolve(confirmed);
this.showDialogResolves.delete(dialogId);
2021-12-21 15:43:35 +01:00
}
// Clean up old promises
2018-04-10 20:20:03 +02:00
this.showDialogResolves.forEach((val, key) => {
const age = new Date().getTime() - val.date.getTime();
if (age > DialogPromiseExpiration) {
this.showDialogResolves.delete(key);
}
});
2018-02-03 05:46:30 +01:00
}
2021-12-21 15:43:35 +01:00
2018-04-14 19:32:56 +02:00
async resolvePasswordDialogPromise(
2018-04-10 20:20:03 +02:00
dialogId: number,
2018-06-30 19:51:20 +02:00
canceled: boolean,
2018-02-16 20:00:41 +01:00
password: string
): Promise<boolean> {
2019-05-30 15:37:09 +02:00
let result = false;
if (this.passwordDialogResolves.has(dialogId)) {
2018-04-23 19:04:11 +02:00
const resolveObj = this.passwordDialogResolves.get(dialogId);
2019-05-30 15:37:09 +02:00
if (await resolveObj.tryResolve(canceled, password)) {
2019-02-27 15:28:16 +01:00
this.passwordDialogResolves.delete(dialogId);
2018-04-23 19:04:11 +02:00
result = true;
2021-12-21 15:43:35 +01:00
}
2018-04-23 19:04:11 +02:00
}
2021-12-21 15:43:35 +01:00
2018-04-23 19:04:11 +02:00
// Clean up old promises
2020-07-09 21:59:47 +02:00
this.passwordDialogResolves.forEach((val, key) => {
2018-04-23 19:04:11 +02:00
const age = new Date().getTime() - val.date.getTime();
2019-08-13 17:57:20 +02:00
if (age > DialogPromiseExpiration) {
2019-02-27 15:28:16 +01:00
this.passwordDialogResolves.delete(key);
}
2018-04-23 19:04:11 +02:00
});
2021-12-21 15:43:35 +01:00
2018-04-23 19:04:11 +02:00
return result;
2018-02-16 20:00:41 +01:00
}
2021-12-21 15:43:35 +01:00
2019-02-27 04:37:21 +01:00
async supportsBiometric() {
2019-08-19 17:58:43 +02:00
const platformInfo = await BrowserApi.getPlatformInfo();
if (platformInfo.os === "android") {
return false;
}
2021-12-21 15:43:35 +01:00
2019-08-19 17:58:43 +02:00
if (this.isFirefox()) {
2019-02-27 04:37:21 +01:00
return parseInt((await browser.runtime.getBrowserInfo()).version.split(".")[0], 10) >= 87;
}
2021-12-21 15:43:35 +01:00
2019-02-27 04:37:21 +01:00
return true;
}
2021-12-21 15:43:35 +01:00
2018-04-10 20:20:03 +02:00
authenticateBiometric() {
return this.biometricCallback();
}
2021-12-21 15:43:35 +01:00
sidebarViewName(): string {
if ((window as any).chrome.sidebarAction && this.isFirefox()) {
return "sidebar";
} else if (this.isOpera() && typeof opr !== "undefined" && opr.sidebarAction) {
return "sidebar_panel";
}
2021-12-21 15:43:35 +01:00
return null;
2018-04-10 20:20:03 +02:00
}
2021-12-21 15:43:35 +01:00
supportsSecureStorage(): boolean {
return false;
}
2021-12-21 15:43:35 +01:00
getDefaultSystemTheme(): Promise<ThemeType.Light | ThemeType.Dark> {
return Promise.resolve(this.prefersColorSchemeDark.matches ? ThemeType.Dark : ThemeType.Light);
2020-07-09 20:14:51 +02:00
}
2021-12-21 15:43:35 +01:00
2018-01-04 22:49:58 +01:00
onDefaultSystemThemeChange(callback: (theme: ThemeType.Light | ThemeType.Dark) => unknown) {
this.prefersColorSchemeDark.addEventListener("change", ({ matches }) => {
callback(matches ? ThemeType.Dark : ThemeType.Light);
});
}
2021-12-21 15:43:35 +01:00
async getEffectiveTheme() {
[Account Switching] Base changes for account switching (#2250) * Pull in jslib * Create new state models * Create browser specific stateService * Remove registration deprecated services, register stateService * Replace usage of deprecated services (user, constants) * Add missing properties to BrowserGroupingsComponentState * Remove StorageService from initFactory * Clear the correct state * Add null check when restoring send-grouping state * add remember email * Initialize stateservice in services.module * Fix 'lock now' not working * Comment to remove setting defaults on install * Pull jslib * Remove setting defaults on install * Bump jslib * Pass the current userId to services when logging out * Bump jslib * Override vaultTimeout default on account addition * Pull latest jslib * Retrieve vaultTimeout from stateService * Record activity per Account * Add userId to logout and add fallback if not present * Register AccountFactory * Pass userId in messages * Base changes for account switching di fixes (#2280) * [bug] Null checks on Account init * [bug] Use same stateService instance for all operations We override the stateService in browser, but currently don't pull the background service into popup and allow jslib to create its own instance of the base StateService for jslib services. This causes a split in in memory state between the three isntances that results in many errors, namely locking not working. * [chore] Update jslib * Pull in jslib * Pull in jslib * Pull in latest jslib to multiple stateservice inits * Check vault states before executing processReload * Adjust iterator * Update native messaging to include the userId (#2290) * Re-Add UserVerificationService * Fix email not being remembered by base component * Improve readability of reloadProcess * Removed unneeded null check * Fix constructor dependency (stateService) * Added missing await * Simplify dependency registration * Fixed typos * Reverted back to simple loop * Use vaultTimeoutService to retrieve Timeout Co-authored-by: Addison Beck <abeck@bitwarden.com> Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
2022-01-27 22:22:51 +01:00
const theme = (await this.stateService.getTheme()) as ThemeType;
if (theme == null || theme === ThemeType.System) {
return this.getDefaultSystemTheme();
} else {
return theme;
2021-12-21 15:43:35 +01:00
}
}
}