mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 09:43:23 +00:00
Apply Prettier (#2238)
This commit is contained in:
@@ -1,98 +1,112 @@
|
||||
import { BrowserApi } from '../browser/browserApi';
|
||||
import { BrowserApi } from "../browser/browserApi";
|
||||
|
||||
import MainBackground from './main.background';
|
||||
import MainBackground from "./main.background";
|
||||
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||
|
||||
export default class CommandsBackground {
|
||||
private isSafari: boolean;
|
||||
private isVivaldi: boolean;
|
||||
private isSafari: boolean;
|
||||
private isVivaldi: boolean;
|
||||
|
||||
constructor(private main: MainBackground, private passwordGenerationService: PasswordGenerationService,
|
||||
private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService) {
|
||||
this.isSafari = this.platformUtilsService.isSafari();
|
||||
this.isVivaldi = this.platformUtilsService.isVivaldi();
|
||||
}
|
||||
constructor(
|
||||
private main: MainBackground,
|
||||
private passwordGenerationService: PasswordGenerationService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private vaultTimeoutService: VaultTimeoutService
|
||||
) {
|
||||
this.isSafari = this.platformUtilsService.isSafari();
|
||||
this.isVivaldi = this.platformUtilsService.isVivaldi();
|
||||
}
|
||||
|
||||
async init() {
|
||||
BrowserApi.messageListener('commands.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||
if (msg.command === 'unlockCompleted' && msg.data.target === 'commands.background') {
|
||||
await this.processCommand(msg.data.commandToRetry.msg.command, msg.data.commandToRetry.sender);
|
||||
}
|
||||
|
||||
if (this.isVivaldi && msg.command === 'keyboardShortcutTriggered' && msg.shortcut) {
|
||||
await this.processCommand(msg.shortcut, sender);
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.isVivaldi && chrome && chrome.commands) {
|
||||
chrome.commands.onCommand.addListener(async (command: string) => {
|
||||
await this.processCommand(command);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async processCommand(command: string, sender?: chrome.runtime.MessageSender) {
|
||||
switch (command) {
|
||||
case 'generate_password':
|
||||
await this.generatePasswordToClipboard();
|
||||
break;
|
||||
case 'autofill_login':
|
||||
await this.autoFillLogin(sender ? sender.tab : null);
|
||||
break;
|
||||
case 'open_popup':
|
||||
await this.openPopup();
|
||||
break;
|
||||
case 'lock_vault':
|
||||
await this.vaultTimeoutService.lock(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async generatePasswordToClipboard() {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
const password = await this.passwordGenerationService.generatePassword(options);
|
||||
this.platformUtilsService.copyToClipboard(password, { window: window });
|
||||
this.passwordGenerationService.addHistory(password);
|
||||
}
|
||||
|
||||
private async autoFillLogin(tab?: chrome.tabs.Tab) {
|
||||
if (!tab) {
|
||||
tab = await BrowserApi.getTabFromCurrentWindowId();
|
||||
async init() {
|
||||
BrowserApi.messageListener(
|
||||
"commands.background",
|
||||
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||
if (msg.command === "unlockCompleted" && msg.data.target === "commands.background") {
|
||||
await this.processCommand(
|
||||
msg.data.commandToRetry.msg.command,
|
||||
msg.data.commandToRetry.sender
|
||||
);
|
||||
}
|
||||
|
||||
if (tab == null) {
|
||||
return;
|
||||
if (this.isVivaldi && msg.command === "keyboardShortcutTriggered" && msg.shortcut) {
|
||||
await this.processCommand(msg.shortcut, sender);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||
commandToRetry: {
|
||||
msg: { command: 'autofill_login' },
|
||||
sender: { tab: tab },
|
||||
},
|
||||
target: 'commands.background',
|
||||
};
|
||||
await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage);
|
||||
if (!this.isVivaldi && chrome && chrome.commands) {
|
||||
chrome.commands.onCommand.addListener(async (command: string) => {
|
||||
await this.processCommand(command);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BrowserApi.tabSendMessageData(tab, 'promptForLogin');
|
||||
return;
|
||||
}
|
||||
private async processCommand(command: string, sender?: chrome.runtime.MessageSender) {
|
||||
switch (command) {
|
||||
case "generate_password":
|
||||
await this.generatePasswordToClipboard();
|
||||
break;
|
||||
case "autofill_login":
|
||||
await this.autoFillLogin(sender ? sender.tab : null);
|
||||
break;
|
||||
case "open_popup":
|
||||
await this.openPopup();
|
||||
break;
|
||||
case "lock_vault":
|
||||
await this.vaultTimeoutService.lock(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await this.main.collectPageDetailsForContentScript(tab, 'autofill_cmd');
|
||||
private async generatePasswordToClipboard() {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
const password = await this.passwordGenerationService.generatePassword(options);
|
||||
this.platformUtilsService.copyToClipboard(password, { window: window });
|
||||
this.passwordGenerationService.addHistory(password);
|
||||
}
|
||||
|
||||
private async autoFillLogin(tab?: chrome.tabs.Tab) {
|
||||
if (!tab) {
|
||||
tab = await BrowserApi.getTabFromCurrentWindowId();
|
||||
}
|
||||
|
||||
private async openPopup() {
|
||||
// Chrome APIs cannot open popup
|
||||
if (!this.isSafari) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.main.openPopup();
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||
commandToRetry: {
|
||||
msg: { command: "autofill_login" },
|
||||
sender: { tab: tab },
|
||||
},
|
||||
target: "commands.background",
|
||||
};
|
||||
await BrowserApi.tabSendMessageData(
|
||||
tab,
|
||||
"addToLockedVaultPendingNotifications",
|
||||
retryMessage
|
||||
);
|
||||
|
||||
BrowserApi.tabSendMessageData(tab, "promptForLogin");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.main.collectPageDetailsForContentScript(tab, "autofill_cmd");
|
||||
}
|
||||
|
||||
private async openPopup() {
|
||||
// Chrome APIs cannot open popup
|
||||
if (!this.isSafari) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.main.openPopup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +1,142 @@
|
||||
import { BrowserApi } from '../browser/browserApi';
|
||||
import { BrowserApi } from "../browser/browserApi";
|
||||
|
||||
import MainBackground from './main.background';
|
||||
import MainBackground from "./main.background";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { EventService } from "jslib-common/abstractions/event.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
|
||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
||||
import { EventType } from 'jslib-common/enums/eventType';
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
||||
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||
import { EventType } from "jslib-common/enums/eventType";
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||
|
||||
export default class ContextMenusBackground {
|
||||
private readonly noopCommandSuffix = 'noop';
|
||||
private contextMenus: any;
|
||||
private readonly noopCommandSuffix = "noop";
|
||||
private contextMenus: any;
|
||||
|
||||
constructor(private main: MainBackground, private cipherService: CipherService,
|
||||
private passwordGenerationService: PasswordGenerationService,
|
||||
private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService,
|
||||
private eventService: EventService, private totpService: TotpService) {
|
||||
this.contextMenus = chrome.contextMenus;
|
||||
constructor(
|
||||
private main: MainBackground,
|
||||
private cipherService: CipherService,
|
||||
private passwordGenerationService: PasswordGenerationService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private eventService: EventService,
|
||||
private totpService: TotpService
|
||||
) {
|
||||
this.contextMenus = chrome.contextMenus;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.contextMenus) {
|
||||
return;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.contextMenus) {
|
||||
return;
|
||||
this.contextMenus.onClicked.addListener(
|
||||
async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => {
|
||||
if (info.menuItemId === "generate-password") {
|
||||
await this.generatePasswordToClipboard();
|
||||
} else if (info.menuItemId === "copy-identifier") {
|
||||
await this.getClickedElement(tab, info.frameId);
|
||||
} else if (
|
||||
info.parentMenuItemId === "autofill" ||
|
||||
info.parentMenuItemId === "copy-username" ||
|
||||
info.parentMenuItemId === "copy-password" ||
|
||||
info.parentMenuItemId === "copy-totp"
|
||||
) {
|
||||
await this.cipherAction(tab, info);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.contextMenus.onClicked.addListener(async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => {
|
||||
if (info.menuItemId === 'generate-password') {
|
||||
await this.generatePasswordToClipboard();
|
||||
} else if (info.menuItemId === 'copy-identifier') {
|
||||
await this.getClickedElement(tab, info.frameId);
|
||||
} else if (info.parentMenuItemId === 'autofill' ||
|
||||
info.parentMenuItemId === 'copy-username' ||
|
||||
info.parentMenuItemId === 'copy-password' ||
|
||||
info.parentMenuItemId === 'copy-totp') {
|
||||
await this.cipherAction(tab, info);
|
||||
}
|
||||
});
|
||||
BrowserApi.messageListener(
|
||||
"contextmenus.background",
|
||||
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||
if (msg.command === "unlockCompleted" && msg.data.target === "contextmenus.background") {
|
||||
await this.cipherAction(
|
||||
msg.data.commandToRetry.sender.tab,
|
||||
msg.data.commandToRetry.msg.data
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BrowserApi.messageListener('contextmenus.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||
if (msg.command === 'unlockCompleted' && msg.data.target === 'contextmenus.background') {
|
||||
await this.cipherAction(msg.data.commandToRetry.sender.tab, msg.data.commandToRetry.msg.data);
|
||||
}
|
||||
});
|
||||
private async generatePasswordToClipboard() {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
const password = await this.passwordGenerationService.generatePassword(options);
|
||||
this.platformUtilsService.copyToClipboard(password, { window: window });
|
||||
this.passwordGenerationService.addHistory(password);
|
||||
}
|
||||
|
||||
private async getClickedElement(tab: chrome.tabs.Tab, frameId: number) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
private async generatePasswordToClipboard() {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
const password = await this.passwordGenerationService.generatePassword(options);
|
||||
this.platformUtilsService.copyToClipboard(password, { window: window });
|
||||
this.passwordGenerationService.addHistory(password);
|
||||
BrowserApi.tabSendMessage(tab, { command: "getClickedElement" }, { frameId: frameId });
|
||||
}
|
||||
|
||||
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
|
||||
const id = info.menuItemId.split("_")[1];
|
||||
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||
commandToRetry: {
|
||||
msg: { command: this.noopCommandSuffix, data: info },
|
||||
sender: { tab: tab },
|
||||
},
|
||||
target: "contextmenus.background",
|
||||
};
|
||||
await BrowserApi.tabSendMessageData(
|
||||
tab,
|
||||
"addToLockedVaultPendingNotifications",
|
||||
retryMessage
|
||||
);
|
||||
|
||||
BrowserApi.tabSendMessageData(tab, "promptForLogin");
|
||||
return;
|
||||
}
|
||||
|
||||
private async getClickedElement(tab: chrome.tabs.Tab, frameId: number) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BrowserApi.tabSendMessage(tab, { command: 'getClickedElement' }, { frameId: frameId });
|
||||
let cipher: CipherView;
|
||||
if (id === this.noopCommandSuffix) {
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url);
|
||||
cipher = ciphers.find((c) => c.reprompt === CipherRepromptType.None);
|
||||
} else {
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
cipher = ciphers.find((c) => c.id === id);
|
||||
}
|
||||
|
||||
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
|
||||
const id = info.menuItemId.split('_')[1];
|
||||
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||
commandToRetry: {
|
||||
msg: { command: this.noopCommandSuffix, data: info },
|
||||
sender: { tab: tab },
|
||||
},
|
||||
target: 'contextmenus.background',
|
||||
};
|
||||
await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage);
|
||||
|
||||
BrowserApi.tabSendMessageData(tab, 'promptForLogin');
|
||||
return;
|
||||
}
|
||||
|
||||
let cipher: CipherView;
|
||||
if (id === this.noopCommandSuffix) {
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url);
|
||||
cipher = ciphers.find(c => c.reprompt === CipherRepromptType.None);
|
||||
} else {
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
cipher = ciphers.find(c => c.id === id);
|
||||
}
|
||||
|
||||
if (cipher == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.parentMenuItemId === 'autofill') {
|
||||
await this.startAutofillPage(tab, cipher);
|
||||
} else if (info.parentMenuItemId === 'copy-username') {
|
||||
this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window });
|
||||
} else if (info.parentMenuItemId === 'copy-password') {
|
||||
this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window });
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
|
||||
} else if (info.parentMenuItemId === 'copy-totp') {
|
||||
const totpValue = await this.totpService.getCode(cipher.login.totp);
|
||||
this.platformUtilsService.copyToClipboard(totpValue, { window: window });
|
||||
}
|
||||
if (cipher == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
private async startAutofillPage(tab: chrome.tabs.Tab, cipher: CipherView) {
|
||||
this.main.loginToAutoFill = cipher;
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BrowserApi.tabSendMessage(tab, {
|
||||
command: 'collectPageDetails',
|
||||
tab: tab,
|
||||
sender: 'contextMenu',
|
||||
});
|
||||
if (info.parentMenuItemId === "autofill") {
|
||||
await this.startAutofillPage(tab, cipher);
|
||||
} else if (info.parentMenuItemId === "copy-username") {
|
||||
this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window });
|
||||
} else if (info.parentMenuItemId === "copy-password") {
|
||||
this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window });
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
|
||||
} else if (info.parentMenuItemId === "copy-totp") {
|
||||
const totpValue = await this.totpService.getCode(cipher.login.totp);
|
||||
this.platformUtilsService.copyToClipboard(totpValue, { window: window });
|
||||
}
|
||||
}
|
||||
|
||||
private async startAutofillPage(tab: chrome.tabs.Tab, cipher: CipherView) {
|
||||
this.main.loginToAutoFill = cipher;
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BrowserApi.tabSendMessage(tab, {
|
||||
command: "collectPageDetails",
|
||||
tab: tab,
|
||||
sender: "contextMenu",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,75 @@
|
||||
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
|
||||
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
import { ConstantsService } from "jslib-common/services/constants.service";
|
||||
|
||||
const IdleInterval = 60 * 5; // 5 minutes
|
||||
|
||||
export default class IdleBackground {
|
||||
private idle: any;
|
||||
private idleTimer: number = null;
|
||||
private idleState = 'active';
|
||||
private idle: any;
|
||||
private idleTimer: number = null;
|
||||
private idleState = "active";
|
||||
|
||||
constructor(private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
|
||||
private notificationsService: NotificationsService) {
|
||||
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
||||
constructor(
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private storageService: StorageService,
|
||||
private notificationsService: NotificationsService
|
||||
) {
|
||||
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.idle) {
|
||||
return;
|
||||
}
|
||||
const idleHandler = (newState: string) => {
|
||||
if (newState === "active") {
|
||||
this.notificationsService.reconnectFromActivity();
|
||||
} else {
|
||||
this.notificationsService.disconnectFromInactivity();
|
||||
}
|
||||
};
|
||||
if (this.idle.onStateChanged && this.idle.setDetectionInterval) {
|
||||
this.idle.setDetectionInterval(IdleInterval);
|
||||
this.idle.onStateChanged.addListener(idleHandler);
|
||||
} else {
|
||||
this.pollIdle(idleHandler);
|
||||
}
|
||||
|
||||
const idleHandler = (newState: string) => {
|
||||
if (newState === 'active') {
|
||||
this.notificationsService.reconnectFromActivity();
|
||||
if (this.idle.onStateChanged) {
|
||||
this.idle.onStateChanged.addListener(async (newState: string) => {
|
||||
if (newState === "locked") {
|
||||
// If the screen is locked or the screensaver activates
|
||||
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
||||
if (timeout === -2) {
|
||||
// On System Lock vault timeout option
|
||||
const action = await this.storageService.get<string>(
|
||||
ConstantsService.vaultTimeoutActionKey
|
||||
);
|
||||
if (action === "logOut") {
|
||||
await this.vaultTimeoutService.logOut();
|
||||
} else {
|
||||
this.notificationsService.disconnectFromInactivity();
|
||||
await this.vaultTimeoutService.lock(true);
|
||||
}
|
||||
};
|
||||
if (this.idle.onStateChanged && this.idle.setDetectionInterval) {
|
||||
this.idle.setDetectionInterval(IdleInterval);
|
||||
this.idle.onStateChanged.addListener(idleHandler);
|
||||
} else {
|
||||
this.pollIdle(idleHandler);
|
||||
}
|
||||
|
||||
if (this.idle.onStateChanged) {
|
||||
this.idle.onStateChanged.addListener(async (newState: string) => {
|
||||
if (newState === 'locked') { // If the screen is locked or the screensaver activates
|
||||
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
||||
if (timeout === -2) { // On System Lock vault timeout option
|
||||
const action = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
||||
if (action === 'logOut') {
|
||||
await this.vaultTimeoutService.logOut();
|
||||
} else {
|
||||
await this.vaultTimeoutService.lock(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private pollIdle(handler: (newState: string) => void) {
|
||||
if (this.idleTimer != null) {
|
||||
window.clearTimeout(this.idleTimer);
|
||||
this.idleTimer = null;
|
||||
}
|
||||
this.idle.queryState(IdleInterval, (state: string) => {
|
||||
if (state !== this.idleState) {
|
||||
this.idleState = state;
|
||||
handler(state);
|
||||
}
|
||||
this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000);
|
||||
});
|
||||
private pollIdle(handler: (newState: string) => void) {
|
||||
if (this.idleTimer != null) {
|
||||
window.clearTimeout(this.idleTimer);
|
||||
this.idleTimer = null;
|
||||
}
|
||||
this.idle.queryState(IdleInterval, (state: string) => {
|
||||
if (state !== this.idleState) {
|
||||
this.idleState = state;
|
||||
handler(state);
|
||||
}
|
||||
this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import NotificationQueueMessage from './notificationQueueMessage';
|
||||
import NotificationQueueMessage from "./notificationQueueMessage";
|
||||
|
||||
export default class AddChangePasswordQueueMessage extends NotificationQueueMessage {
|
||||
cipherId: string;
|
||||
newPassword: string;
|
||||
cipherId: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import NotificationQueueMessage from './notificationQueueMessage';
|
||||
import NotificationQueueMessage from "./notificationQueueMessage";
|
||||
|
||||
export default class AddLoginQueueMessage extends NotificationQueueMessage {
|
||||
username: string;
|
||||
password: string;
|
||||
uri: string;
|
||||
username: string;
|
||||
password: string;
|
||||
uri: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default class AddLoginRuntimeMessage {
|
||||
username: string;
|
||||
password: string;
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default class ChangePasswordRuntimeMessage {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
url: string;
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default class LockedVaultPendingNotificationsItem {
|
||||
commandToRetry: {
|
||||
msg: any;
|
||||
sender: chrome.runtime.MessageSender;
|
||||
};
|
||||
target: string;
|
||||
commandToRetry: {
|
||||
msg: any;
|
||||
sender: chrome.runtime.MessageSender;
|
||||
};
|
||||
target: string;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { NotificationQueueMessageType } from './notificationQueueMessageType';
|
||||
import { NotificationQueueMessageType } from "./notificationQueueMessageType";
|
||||
|
||||
export default class NotificationQueueMessage {
|
||||
type: NotificationQueueMessageType;
|
||||
domain: string;
|
||||
tabId: number;
|
||||
expires: Date;
|
||||
wasVaultLocked: boolean;
|
||||
type: NotificationQueueMessageType;
|
||||
domain: string;
|
||||
tabId: number;
|
||||
expires: Date;
|
||||
wasVaultLocked: boolean;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export enum NotificationQueueMessageType {
|
||||
addLogin = 'addLogin',
|
||||
changePassword = 'changePassword',
|
||||
addLogin = "addLogin",
|
||||
changePassword = "changePassword",
|
||||
}
|
||||
|
||||
@@ -1,333 +1,350 @@
|
||||
import { AppIdService } from 'jslib-common/abstractions/appId.service';
|
||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
import { AppIdService } from "jslib-common/abstractions/appId.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||
import { UserService } from "jslib-common/abstractions/user.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
import { ConstantsService } from "jslib-common/services/constants.service";
|
||||
|
||||
import { Utils } from 'jslib-common/misc/utils';
|
||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
|
||||
|
||||
import { BrowserApi } from '../browser/browserApi';
|
||||
import RuntimeBackground from './runtime.background';
|
||||
import { BrowserApi } from "../browser/browserApi";
|
||||
import RuntimeBackground from "./runtime.background";
|
||||
|
||||
const MessageValidTimeout = 10 * 1000;
|
||||
const EncryptionAlgorithm = 'sha1';
|
||||
const EncryptionAlgorithm = "sha1";
|
||||
|
||||
export class NativeMessagingBackground {
|
||||
private connected = false;
|
||||
private connecting: boolean;
|
||||
private port: browser.runtime.Port | chrome.runtime.Port;
|
||||
private connected = false;
|
||||
private connecting: boolean;
|
||||
private port: browser.runtime.Port | chrome.runtime.Port;
|
||||
|
||||
private resolver: any = null;
|
||||
private privateKey: ArrayBuffer = null;
|
||||
private publicKey: ArrayBuffer = null;
|
||||
private secureSetupResolve: any = null;
|
||||
private sharedSecret: SymmetricCryptoKey;
|
||||
private appId: string;
|
||||
private validatingFingerprint: boolean;
|
||||
private resolver: any = null;
|
||||
private privateKey: ArrayBuffer = null;
|
||||
private publicKey: ArrayBuffer = null;
|
||||
private secureSetupResolve: any = null;
|
||||
private sharedSecret: SymmetricCryptoKey;
|
||||
private appId: string;
|
||||
private validatingFingerprint: boolean;
|
||||
|
||||
constructor(private storageService: StorageService, private cryptoService: CryptoService,
|
||||
private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService,
|
||||
private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService,
|
||||
private messagingService: MessagingService, private appIdService: AppIdService,
|
||||
private platformUtilsService: PlatformUtilsService) {
|
||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
||||
constructor(
|
||||
private storageService: StorageService,
|
||||
private cryptoService: CryptoService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private runtimeBackground: RuntimeBackground,
|
||||
private i18nService: I18nService,
|
||||
private userService: UserService,
|
||||
private messagingService: MessagingService,
|
||||
private appIdService: AppIdService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
) {
|
||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
||||
|
||||
if (chrome?.permissions?.onAdded) {
|
||||
// Reload extension to activate nativeMessaging
|
||||
chrome.permissions.onAdded.addListener(permissions => {
|
||||
BrowserApi.reloadExtension(null);
|
||||
});
|
||||
if (chrome?.permissions?.onAdded) {
|
||||
// Reload extension to activate nativeMessaging
|
||||
chrome.permissions.onAdded.addListener((permissions) => {
|
||||
BrowserApi.reloadExtension(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.appId = await this.appIdService.getAppId();
|
||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.port = BrowserApi.connectNative("com.8bit.bitwarden");
|
||||
|
||||
this.connecting = true;
|
||||
|
||||
const connectedCallback = () => {
|
||||
this.connected = true;
|
||||
this.connecting = false;
|
||||
resolve();
|
||||
};
|
||||
|
||||
// Safari has a bundled native component which is always available, no need to
|
||||
// check if the desktop app is running.
|
||||
if (this.platformUtilsService.isSafari()) {
|
||||
connectedCallback();
|
||||
}
|
||||
|
||||
this.port.onMessage.addListener(async (message: any) => {
|
||||
switch (message.command) {
|
||||
case "connected":
|
||||
connectedCallback();
|
||||
break;
|
||||
case "disconnected":
|
||||
if (this.connecting) {
|
||||
this.messagingService.send("showDialog", {
|
||||
text: this.i18nService.t("startDesktopDesc"),
|
||||
title: this.i18nService.t("startDesktopTitle"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "error",
|
||||
});
|
||||
reject();
|
||||
}
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.appId = await this.appIdService.getAppId();
|
||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.port = BrowserApi.connectNative('com.8bit.bitwarden');
|
||||
|
||||
this.connecting = true;
|
||||
|
||||
const connectedCallback = () => {
|
||||
this.connected = true;
|
||||
this.connecting = false;
|
||||
resolve();
|
||||
};
|
||||
|
||||
// Safari has a bundled native component which is always available, no need to
|
||||
// check if the desktop app is running.
|
||||
if (this.platformUtilsService.isSafari()) {
|
||||
connectedCallback();
|
||||
this.connected = false;
|
||||
this.port.disconnect();
|
||||
break;
|
||||
case "setupEncryption":
|
||||
// Ignore since it belongs to another device
|
||||
if (message.appId !== this.appId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.port.onMessage.addListener(async (message: any) => {
|
||||
switch (message.command) {
|
||||
case 'connected':
|
||||
connectedCallback();
|
||||
break;
|
||||
case 'disconnected':
|
||||
if (this.connecting) {
|
||||
this.messagingService.send('showDialog', {
|
||||
text: this.i18nService.t('startDesktopDesc'),
|
||||
title: this.i18nService.t('startDesktopTitle'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'error',
|
||||
});
|
||||
reject();
|
||||
}
|
||||
this.connected = false;
|
||||
this.port.disconnect();
|
||||
break;
|
||||
case 'setupEncryption':
|
||||
// Ignore since it belongs to another device
|
||||
if (message.appId !== this.appId) {
|
||||
return;
|
||||
}
|
||||
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
|
||||
const decrypted = await this.cryptoFunctionService.rsaDecrypt(
|
||||
encrypted.buffer,
|
||||
this.privateKey,
|
||||
EncryptionAlgorithm
|
||||
);
|
||||
|
||||
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
|
||||
const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm);
|
||||
|
||||
if (this.validatingFingerprint) {
|
||||
this.validatingFingerprint = false;
|
||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, true);
|
||||
}
|
||||
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
||||
this.secureSetupResolve();
|
||||
break;
|
||||
case 'invalidateEncryption':
|
||||
// Ignore since it belongs to another device
|
||||
if (message.appId !== this.appId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sharedSecret = null;
|
||||
this.privateKey = null;
|
||||
this.connected = false;
|
||||
|
||||
this.messagingService.send('showDialog', {
|
||||
text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'),
|
||||
title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'error',
|
||||
});
|
||||
break;
|
||||
case 'verifyFingerprint': {
|
||||
if (this.sharedSecret == null) {
|
||||
this.validatingFingerprint = true;
|
||||
this.showFingerprintDialog();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'wrongUserId':
|
||||
this.showWrongUserDialog();
|
||||
default:
|
||||
// Ignore since it belongs to another device
|
||||
if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onMessage(message.message);
|
||||
}
|
||||
});
|
||||
|
||||
this.port.onDisconnect.addListener((p: any) => {
|
||||
let error;
|
||||
if (BrowserApi.isWebExtensionsApi) {
|
||||
error = p.error.message;
|
||||
} else {
|
||||
error = chrome.runtime.lastError.message;
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
this.messagingService.send('showDialog', {
|
||||
text: this.i18nService.t('desktopIntegrationDisabledDesc'),
|
||||
title: this.i18nService.t('desktopIntegrationDisabledTitle'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
this.sharedSecret = null;
|
||||
this.privateKey = null;
|
||||
this.connected = false;
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
showWrongUserDialog() {
|
||||
this.messagingService.send('showDialog', {
|
||||
text: this.i18nService.t('nativeMessagingWrongUserDesc'),
|
||||
title: this.i18nService.t('nativeMessagingWrongUserTitle'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
async send(message: any) {
|
||||
if (!this.connected) {
|
||||
await this.connect();
|
||||
}
|
||||
|
||||
if (this.platformUtilsService.isSafari()) {
|
||||
this.postMessage(message);
|
||||
} else {
|
||||
this.postMessage({appId: this.appId, message: await this.encryptMessage(message)});
|
||||
}
|
||||
}
|
||||
|
||||
async encryptMessage(message: any) {
|
||||
if (this.sharedSecret == null) {
|
||||
await this.secureCommunication();
|
||||
}
|
||||
|
||||
message.timestamp = Date.now();
|
||||
|
||||
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
|
||||
}
|
||||
|
||||
getResponse(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.resolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
private postMessage(message: any) {
|
||||
// Wrap in try-catch to when the port disconnected without triggering `onDisconnect`.
|
||||
try {
|
||||
this.port.postMessage(message);
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
console.error("NativeMessaging port disconnected, disconnecting.");
|
||||
if (this.validatingFingerprint) {
|
||||
this.validatingFingerprint = false;
|
||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, true);
|
||||
}
|
||||
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
||||
this.secureSetupResolve();
|
||||
break;
|
||||
case "invalidateEncryption":
|
||||
// Ignore since it belongs to another device
|
||||
if (message.appId !== this.appId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sharedSecret = null;
|
||||
this.privateKey = null;
|
||||
this.connected = false;
|
||||
|
||||
this.messagingService.send('showDialog', {
|
||||
text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'),
|
||||
title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'error',
|
||||
this.messagingService.send("showDialog", {
|
||||
text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"),
|
||||
title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "error",
|
||||
});
|
||||
break;
|
||||
case "verifyFingerprint": {
|
||||
if (this.sharedSecret == null) {
|
||||
this.validatingFingerprint = true;
|
||||
this.showFingerprintDialog();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "wrongUserId":
|
||||
this.showWrongUserDialog();
|
||||
default:
|
||||
// Ignore since it belongs to another device
|
||||
if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onMessage(message.message);
|
||||
}
|
||||
});
|
||||
|
||||
this.port.onDisconnect.addListener((p: any) => {
|
||||
let error;
|
||||
if (BrowserApi.isWebExtensionsApi) {
|
||||
error = p.error.message;
|
||||
} else {
|
||||
error = chrome.runtime.lastError.message;
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
this.messagingService.send("showDialog", {
|
||||
text: this.i18nService.t("desktopIntegrationDisabledDesc"),
|
||||
title: this.i18nService.t("desktopIntegrationDisabledTitle"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
this.sharedSecret = null;
|
||||
this.privateKey = null;
|
||||
this.connected = false;
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
showWrongUserDialog() {
|
||||
this.messagingService.send("showDialog", {
|
||||
text: this.i18nService.t("nativeMessagingWrongUserDesc"),
|
||||
title: this.i18nService.t("nativeMessagingWrongUserTitle"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
|
||||
async send(message: any) {
|
||||
if (!this.connected) {
|
||||
await this.connect();
|
||||
}
|
||||
|
||||
private async onMessage(rawMessage: any) {
|
||||
let message = rawMessage;
|
||||
if (!this.platformUtilsService.isSafari()) {
|
||||
message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
|
||||
if (this.platformUtilsService.isSafari()) {
|
||||
this.postMessage(message);
|
||||
} else {
|
||||
this.postMessage({ appId: this.appId, message: await this.encryptMessage(message) });
|
||||
}
|
||||
}
|
||||
|
||||
async encryptMessage(message: any) {
|
||||
if (this.sharedSecret == null) {
|
||||
await this.secureCommunication();
|
||||
}
|
||||
|
||||
message.timestamp = Date.now();
|
||||
|
||||
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
|
||||
}
|
||||
|
||||
getResponse(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.resolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
private postMessage(message: any) {
|
||||
// Wrap in try-catch to when the port disconnected without triggering `onDisconnect`.
|
||||
try {
|
||||
this.port.postMessage(message);
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
console.error("NativeMessaging port disconnected, disconnecting.");
|
||||
|
||||
this.sharedSecret = null;
|
||||
this.privateKey = null;
|
||||
this.connected = false;
|
||||
|
||||
this.messagingService.send("showDialog", {
|
||||
text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"),
|
||||
title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async onMessage(rawMessage: any) {
|
||||
let message = rawMessage;
|
||||
if (!this.platformUtilsService.isSafari()) {
|
||||
message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
|
||||
}
|
||||
|
||||
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
|
||||
// tslint:disable-next-line
|
||||
console.error("NativeMessage is to old, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.command) {
|
||||
case "biometricUnlock":
|
||||
await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance);
|
||||
|
||||
if (message.response === "not enabled") {
|
||||
this.messagingService.send("showDialog", {
|
||||
text: this.i18nService.t("biometricsNotEnabledDesc"),
|
||||
title: this.i18nService.t("biometricsNotEnabledTitle"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "error",
|
||||
});
|
||||
break;
|
||||
} else if (message.response === "not supported") {
|
||||
this.messagingService.send("showDialog", {
|
||||
text: this.i18nService.t("biometricsNotSupportedDesc"),
|
||||
title: this.i18nService.t("biometricsNotSupportedTitle"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "error",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
|
||||
const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey);
|
||||
if (enabled === null || enabled === false) {
|
||||
if (message.response === "unlocked") {
|
||||
await this.storageService.save(ConstantsService.biometricUnlockKey, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore unlock if already unlockeded
|
||||
if (!this.vaultTimeoutService.biometricLocked) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.response === "unlocked") {
|
||||
await this.cryptoService.setKey(
|
||||
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)
|
||||
);
|
||||
|
||||
// Verify key is correct by attempting to decrypt a secret
|
||||
try {
|
||||
await this.cryptoService.getFingerprint(await this.userService.getUserId());
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
console.error('NativeMessage is to old, ignoring.');
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.command) {
|
||||
case 'biometricUnlock':
|
||||
await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance);
|
||||
|
||||
if (message.response === 'not enabled') {
|
||||
this.messagingService.send('showDialog', {
|
||||
text: this.i18nService.t('biometricsNotEnabledDesc'),
|
||||
title: this.i18nService.t('biometricsNotEnabledTitle'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'error',
|
||||
});
|
||||
break;
|
||||
} else if (message.response === 'not supported') {
|
||||
this.messagingService.send('showDialog', {
|
||||
text: this.i18nService.t('biometricsNotSupportedDesc'),
|
||||
title: this.i18nService.t('biometricsNotSupportedTitle'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'error',
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey);
|
||||
if (enabled === null || enabled === false) {
|
||||
if (message.response === 'unlocked') {
|
||||
await this.storageService.save(ConstantsService.biometricUnlockKey, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore unlock if already unlockeded
|
||||
if (!this.vaultTimeoutService.biometricLocked) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.response === 'unlocked') {
|
||||
await this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer));
|
||||
|
||||
// Verify key is correct by attempting to decrypt a secret
|
||||
try {
|
||||
await this.cryptoService.getFingerprint(await this.userService.getUserId());
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
console.error('Unable to verify key:', e);
|
||||
await this.cryptoService.clearKey();
|
||||
this.showWrongUserDialog();
|
||||
|
||||
message = false;
|
||||
break;
|
||||
}
|
||||
|
||||
this.vaultTimeoutService.biometricLocked = false;
|
||||
this.runtimeBackground.processMessage({command: 'unlocked'}, null, null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// tslint:disable-next-line
|
||||
console.error('NativeMessage, got unknown command: ', message.command);
|
||||
}
|
||||
|
||||
if (this.resolver) {
|
||||
this.resolver(message);
|
||||
console.error("Unable to verify key:", e);
|
||||
await this.cryptoService.clearKey();
|
||||
this.showWrongUserDialog();
|
||||
|
||||
message = false;
|
||||
break;
|
||||
}
|
||||
|
||||
this.vaultTimeoutService.biometricLocked = false;
|
||||
this.runtimeBackground.processMessage({ command: "unlocked" }, null, null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// tslint:disable-next-line
|
||||
console.error("NativeMessage, got unknown command: ", message.command);
|
||||
}
|
||||
|
||||
private async secureCommunication() {
|
||||
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
if (this.resolver) {
|
||||
this.resolver(message);
|
||||
}
|
||||
}
|
||||
|
||||
this.sendUnencrypted({
|
||||
command: 'setupEncryption',
|
||||
publicKey: Utils.fromBufferToB64(publicKey),
|
||||
userId: await this.userService.getUserId(),
|
||||
});
|
||||
private async secureCommunication() {
|
||||
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
|
||||
return new Promise((resolve, reject) => this.secureSetupResolve = resolve);
|
||||
this.sendUnencrypted({
|
||||
command: "setupEncryption",
|
||||
publicKey: Utils.fromBufferToB64(publicKey),
|
||||
userId: await this.userService.getUserId(),
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => (this.secureSetupResolve = resolve));
|
||||
}
|
||||
|
||||
private async sendUnencrypted(message: any) {
|
||||
if (!this.connected) {
|
||||
await this.connect();
|
||||
}
|
||||
|
||||
private async sendUnencrypted(message: any) {
|
||||
if (!this.connected) {
|
||||
await this.connect();
|
||||
}
|
||||
message.timestamp = Date.now();
|
||||
|
||||
message.timestamp = Date.now();
|
||||
this.postMessage({ appId: this.appId, message: message });
|
||||
}
|
||||
|
||||
this.postMessage({appId: this.appId, message: message});
|
||||
}
|
||||
private async showFingerprintDialog() {
|
||||
const fingerprint = (
|
||||
await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)
|
||||
).join(" ");
|
||||
|
||||
private async showFingerprintDialog() {
|
||||
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' ');
|
||||
|
||||
this.messagingService.send('showDialog', {
|
||||
html: `${this.i18nService.t('desktopIntegrationVerificationText')}<br><br><strong>${fingerprint}</strong>`,
|
||||
title: this.i18nService.t('desktopSyncVerificationTitle'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
this.messagingService.send("showDialog", {
|
||||
html: `${this.i18nService.t(
|
||||
"desktopIntegrationVerificationText"
|
||||
)}<br><br><strong>${fingerprint}</strong>`,
|
||||
title: this.i18nService.t("desktopSyncVerificationTitle"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,417 +1,460 @@
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
|
||||
import { LoginView } from 'jslib-common/models/view/loginView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
import { LoginUriView } from "jslib-common/models/view/loginUriView";
|
||||
import { LoginView } from "jslib-common/models/view/loginView";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||
import { UserService } from "jslib-common/abstractions/user.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
import { ConstantsService } from "jslib-common/services/constants.service";
|
||||
|
||||
import { AutofillService } from '../services/abstractions/autofill.service';
|
||||
import { AutofillService } from "../services/abstractions/autofill.service";
|
||||
|
||||
import { BrowserApi } from '../browser/browserApi';
|
||||
import { BrowserApi } from "../browser/browserApi";
|
||||
|
||||
import MainBackground from './main.background';
|
||||
import MainBackground from "./main.background";
|
||||
|
||||
import { Utils } from 'jslib-common/misc/utils';
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
|
||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
||||
import { PolicyType } from "jslib-common/enums/policyType";
|
||||
|
||||
import AddChangePasswordQueueMessage from './models/addChangePasswordQueueMessage';
|
||||
import AddLoginQueueMessage from './models/addLoginQueueMessage';
|
||||
import AddLoginRuntimeMessage from './models/addLoginRuntimeMessage';
|
||||
import ChangePasswordRuntimeMessage from './models/changePasswordRuntimeMessage';
|
||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
||||
import { NotificationQueueMessageType } from './models/notificationQueueMessageType';
|
||||
import AddChangePasswordQueueMessage from "./models/addChangePasswordQueueMessage";
|
||||
import AddLoginQueueMessage from "./models/addLoginQueueMessage";
|
||||
import AddLoginRuntimeMessage from "./models/addLoginRuntimeMessage";
|
||||
import ChangePasswordRuntimeMessage from "./models/changePasswordRuntimeMessage";
|
||||
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||
import { NotificationQueueMessageType } from "./models/notificationQueueMessageType";
|
||||
|
||||
export default class NotificationBackground {
|
||||
private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = [];
|
||||
|
||||
private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = [];
|
||||
constructor(
|
||||
private main: MainBackground,
|
||||
private autofillService: AutofillService,
|
||||
private cipherService: CipherService,
|
||||
private storageService: StorageService,
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private policyService: PolicyService,
|
||||
private folderService: FolderService,
|
||||
private userService: UserService
|
||||
) {}
|
||||
|
||||
constructor(private main: MainBackground, private autofillService: AutofillService,
|
||||
private cipherService: CipherService, private storageService: StorageService,
|
||||
private vaultTimeoutService: VaultTimeoutService, private policyService: PolicyService,
|
||||
private folderService: FolderService, private userService: UserService) {
|
||||
async init() {
|
||||
if (chrome.runtime == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (chrome.runtime == null) {
|
||||
return;
|
||||
BrowserApi.messageListener(
|
||||
"notification.background",
|
||||
async (msg: any, sender: chrome.runtime.MessageSender) => {
|
||||
await this.processMessage(msg, sender);
|
||||
}
|
||||
);
|
||||
|
||||
this.cleanupNotificationQueue();
|
||||
}
|
||||
|
||||
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
|
||||
switch (msg.command) {
|
||||
case "unlockCompleted":
|
||||
if (msg.data.target !== "notification.background") {
|
||||
return;
|
||||
}
|
||||
|
||||
BrowserApi.messageListener('notification.background', async (msg: any, sender: chrome.runtime.MessageSender) => {
|
||||
await this.processMessage(msg, sender);
|
||||
});
|
||||
|
||||
this.cleanupNotificationQueue();
|
||||
}
|
||||
|
||||
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
|
||||
switch (msg.command) {
|
||||
case 'unlockCompleted':
|
||||
if (msg.data.target !== 'notification.background') {
|
||||
return;
|
||||
}
|
||||
await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender);
|
||||
break;
|
||||
case 'bgGetDataForTab':
|
||||
await this.getDataForTab(sender.tab, msg.responseCommand);
|
||||
break;
|
||||
case 'bgCloseNotificationBar':
|
||||
await BrowserApi.tabSendMessageData(sender.tab, 'closeNotificationBar');
|
||||
break;
|
||||
case 'bgAdjustNotificationBar':
|
||||
await BrowserApi.tabSendMessageData(sender.tab, 'adjustNotificationBar', msg.data);
|
||||
break;
|
||||
case 'bgAddLogin':
|
||||
await this.addLogin(msg.login, sender.tab);
|
||||
break;
|
||||
case 'bgChangedPassword':
|
||||
await this.changedPassword(msg.data, sender.tab);
|
||||
break;
|
||||
case 'bgAddClose':
|
||||
case 'bgChangeClose':
|
||||
this.removeTabFromNotificationQueue(sender.tab);
|
||||
break;
|
||||
case 'bgAddSave':
|
||||
case 'bgChangeSave':
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||
commandToRetry: {
|
||||
msg: msg,
|
||||
sender: sender,
|
||||
},
|
||||
target: 'notification.background',
|
||||
};
|
||||
await BrowserApi.tabSendMessageData(sender.tab, 'addToLockedVaultPendingNotifications', retryMessage);
|
||||
await BrowserApi.tabSendMessageData(sender.tab, 'promptForLogin');
|
||||
return;
|
||||
}
|
||||
await this.saveOrUpdateCredentials(sender.tab, msg.folder);
|
||||
break;
|
||||
case 'bgNeverSave':
|
||||
await this.saveNever(sender.tab);
|
||||
break;
|
||||
case 'collectPageDetailsResponse':
|
||||
switch (msg.sender) {
|
||||
case 'notificationBar':
|
||||
const forms = this.autofillService.getFormsWithPasswordFields(msg.details);
|
||||
await BrowserApi.tabSendMessageData(msg.tab, 'notificationBarPageDetails', {
|
||||
details: msg.details,
|
||||
forms: forms,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender);
|
||||
break;
|
||||
case "bgGetDataForTab":
|
||||
await this.getDataForTab(sender.tab, msg.responseCommand);
|
||||
break;
|
||||
case "bgCloseNotificationBar":
|
||||
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
|
||||
break;
|
||||
case "bgAdjustNotificationBar":
|
||||
await BrowserApi.tabSendMessageData(sender.tab, "adjustNotificationBar", msg.data);
|
||||
break;
|
||||
case "bgAddLogin":
|
||||
await this.addLogin(msg.login, sender.tab);
|
||||
break;
|
||||
case "bgChangedPassword":
|
||||
await this.changedPassword(msg.data, sender.tab);
|
||||
break;
|
||||
case "bgAddClose":
|
||||
case "bgChangeClose":
|
||||
this.removeTabFromNotificationQueue(sender.tab);
|
||||
break;
|
||||
case "bgAddSave":
|
||||
case "bgChangeSave":
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||
commandToRetry: {
|
||||
msg: msg,
|
||||
sender: sender,
|
||||
},
|
||||
target: "notification.background",
|
||||
};
|
||||
await BrowserApi.tabSendMessageData(
|
||||
sender.tab,
|
||||
"addToLockedVaultPendingNotifications",
|
||||
retryMessage
|
||||
);
|
||||
await BrowserApi.tabSendMessageData(sender.tab, "promptForLogin");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise<void> {
|
||||
if (this.notificationQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab != null) {
|
||||
this.doNotificationQueueCheck(tab);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTab = await BrowserApi.getTabFromCurrentWindow();
|
||||
if (currentTab != null) {
|
||||
this.doNotificationQueueCheck(currentTab);
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupNotificationQueue() {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
if (this.notificationQueue[i].expires < new Date()) {
|
||||
this.notificationQueue.splice(i, 1);
|
||||
}
|
||||
}
|
||||
setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes
|
||||
}
|
||||
|
||||
private doNotificationQueueCheck(tab: chrome.tabs.Tab): void {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabDomain = Utils.getDomain(tab.url);
|
||||
if (tabDomain == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.notificationQueue.length; i++) {
|
||||
if (this.notificationQueue[i].tabId !== tab.id || this.notificationQueue[i].domain !== tabDomain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) {
|
||||
BrowserApi.tabSendMessageData(tab, 'openNotificationBar', {
|
||||
type: 'add',
|
||||
typeData: {
|
||||
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
||||
},
|
||||
});
|
||||
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) {
|
||||
BrowserApi.tabSendMessageData(tab, 'openNotificationBar', {
|
||||
type: 'change',
|
||||
typeData: {
|
||||
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
||||
},
|
||||
});
|
||||
}
|
||||
await this.saveOrUpdateCredentials(sender.tab, msg.folder);
|
||||
break;
|
||||
case "bgNeverSave":
|
||||
await this.saveNever(sender.tab);
|
||||
break;
|
||||
case "collectPageDetailsResponse":
|
||||
switch (msg.sender) {
|
||||
case "notificationBar":
|
||||
const forms = this.autofillService.getFormsWithPasswordFields(msg.details);
|
||||
await BrowserApi.tabSendMessageData(msg.tab, "notificationBarPageDetails", {
|
||||
details: msg.details,
|
||||
forms: forms,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise<void> {
|
||||
if (this.notificationQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
if (this.notificationQueue[i].tabId === tab.id) {
|
||||
this.notificationQueue.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (tab != null) {
|
||||
this.doNotificationQueueCheck(tab);
|
||||
return;
|
||||
}
|
||||
|
||||
private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) {
|
||||
if (!await this.userService.isAuthenticated()) {
|
||||
return;
|
||||
}
|
||||
const currentTab = await BrowserApi.getTabFromCurrentWindow();
|
||||
if (currentTab != null) {
|
||||
this.doNotificationQueueCheck(currentTab);
|
||||
}
|
||||
}
|
||||
|
||||
const loginDomain = Utils.getDomain(loginInfo.url);
|
||||
if (loginDomain == null) {
|
||||
return;
|
||||
}
|
||||
private cleanupNotificationQueue() {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
if (this.notificationQueue[i].expires < new Date()) {
|
||||
this.notificationQueue.splice(i, 1);
|
||||
}
|
||||
}
|
||||
setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes
|
||||
}
|
||||
|
||||
let normalizedUsername = loginInfo.username;
|
||||
if (normalizedUsername != null) {
|
||||
normalizedUsername = normalizedUsername.toLowerCase();
|
||||
}
|
||||
|
||||
const disabledAddLogin = await this.storageService.get<boolean>(ConstantsService.disableAddLoginNotificationKey);
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
if (disabledAddLogin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await this.allowPersonalOwnership()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
|
||||
const usernameMatches = ciphers.filter(c =>
|
||||
c.login.username != null && c.login.username.toLowerCase() === normalizedUsername);
|
||||
if (usernameMatches.length === 0) {
|
||||
if (disabledAddLogin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await this.allowPersonalOwnership()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
|
||||
|
||||
} else if (usernameMatches.length === 1 && usernameMatches[0].login.password !== loginInfo.password) {
|
||||
const disabledChangePassword = await this.storageService.get<boolean>(
|
||||
ConstantsService.disableChangedPasswordNotificationKey);
|
||||
if (disabledChangePassword) {
|
||||
return;
|
||||
}
|
||||
this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, loginInfo.password, tab);
|
||||
}
|
||||
private doNotificationQueueCheck(tab: chrome.tabs.Tab): void {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
private async pushAddLoginToQueue(loginDomain: string, loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) {
|
||||
// remove any old messages for this tab
|
||||
this.removeTabFromNotificationQueue(tab);
|
||||
const message: AddLoginQueueMessage = {
|
||||
type: NotificationQueueMessageType.addLogin,
|
||||
username: loginInfo.username,
|
||||
password: loginInfo.password,
|
||||
domain: loginDomain,
|
||||
uri: loginInfo.url,
|
||||
tabId: tab.id,
|
||||
expires: new Date((new Date()).getTime() + 5 * 60000), // 5 minutes
|
||||
wasVaultLocked: isVaultLocked,
|
||||
};
|
||||
this.notificationQueue.push(message);
|
||||
await this.checkNotificationQueue(tab);
|
||||
const tabDomain = Utils.getDomain(tab.url);
|
||||
if (tabDomain == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
private async changedPassword(changeData: ChangePasswordRuntimeMessage, tab: chrome.tabs.Tab) {
|
||||
const loginDomain = Utils.getDomain(changeData.url);
|
||||
if (loginDomain == null) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.notificationQueue.length; i++) {
|
||||
if (
|
||||
this.notificationQueue[i].tabId !== tab.id ||
|
||||
this.notificationQueue[i].domain !== tabDomain
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true);
|
||||
return;
|
||||
}
|
||||
if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) {
|
||||
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
|
||||
type: "add",
|
||||
typeData: {
|
||||
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
||||
},
|
||||
});
|
||||
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) {
|
||||
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
|
||||
type: "change",
|
||||
typeData: {
|
||||
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let id: string = null;
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
|
||||
if (changeData.currentPassword != null) {
|
||||
const passwordMatches = ciphers.filter(c => c.login.password === changeData.currentPassword);
|
||||
if (passwordMatches.length === 1) {
|
||||
id = passwordMatches[0].id;
|
||||
}
|
||||
} else if (ciphers.length === 1) {
|
||||
id = ciphers[0].id;
|
||||
}
|
||||
if (id != null) {
|
||||
this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab);
|
||||
}
|
||||
private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
if (this.notificationQueue[i].tabId === tab.id) {
|
||||
this.notificationQueue.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) {
|
||||
if (!(await this.userService.isAuthenticated())) {
|
||||
return;
|
||||
}
|
||||
|
||||
private async pushChangePasswordToQueue(cipherId: string, loginDomain: string, newPassword: string, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) {
|
||||
// remove any old messages for this tab
|
||||
this.removeTabFromNotificationQueue(tab);
|
||||
const message: AddChangePasswordQueueMessage = {
|
||||
type: NotificationQueueMessageType.changePassword,
|
||||
cipherId: cipherId,
|
||||
newPassword: newPassword,
|
||||
domain: loginDomain,
|
||||
tabId: tab.id,
|
||||
expires: new Date((new Date()).getTime() + 5 * 60000), // 5 minutes
|
||||
wasVaultLocked: isVaultLocked,
|
||||
};
|
||||
this.notificationQueue.push(message);
|
||||
await this.checkNotificationQueue(tab);
|
||||
const loginDomain = Utils.getDomain(loginInfo.url);
|
||||
if (loginDomain == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
const queueMessage = this.notificationQueue[i];
|
||||
if (queueMessage.tabId !== tab.id ||
|
||||
(queueMessage.type !== NotificationQueueMessageType.addLogin && queueMessage.type !== NotificationQueueMessageType.changePassword)) {
|
||||
continue;
|
||||
}
|
||||
let normalizedUsername = loginInfo.username;
|
||||
if (normalizedUsername != null) {
|
||||
normalizedUsername = normalizedUsername.toLowerCase();
|
||||
}
|
||||
|
||||
const tabDomain = Utils.getDomain(tab.url);
|
||||
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
||||
continue;
|
||||
}
|
||||
const disabledAddLogin = await this.storageService.get<boolean>(
|
||||
ConstantsService.disableAddLoginNotificationKey
|
||||
);
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
if (disabledAddLogin) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationQueue.splice(i, 1);
|
||||
BrowserApi.tabSendMessageData(tab, 'closeNotificationBar');
|
||||
if (!(await this.allowPersonalOwnership())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (queueMessage.type === NotificationQueueMessageType.changePassword) {
|
||||
const message = (queueMessage as AddChangePasswordQueueMessage);
|
||||
const cipher = await this.getDecryptedCipherById(message.cipherId);
|
||||
if (cipher == null) {
|
||||
return;
|
||||
}
|
||||
await this.updateCipher(cipher, message.newPassword);
|
||||
return;
|
||||
}
|
||||
this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!queueMessage.wasVaultLocked) {
|
||||
await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId);
|
||||
}
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
|
||||
const usernameMatches = ciphers.filter(
|
||||
(c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername
|
||||
);
|
||||
if (usernameMatches.length === 0) {
|
||||
if (disabledAddLogin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||
if (queueMessage.type === NotificationQueueMessageType.addLogin && queueMessage.wasVaultLocked === true) {
|
||||
const message = (queueMessage as AddLoginQueueMessage);
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri);
|
||||
const usernameMatches = ciphers.filter(c => c.login.username != null &&
|
||||
c.login.username.toLowerCase() === message.username);
|
||||
if (!(await this.allowPersonalOwnership())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (usernameMatches.length >= 1) {
|
||||
await this.updateCipher(usernameMatches[0], message.password);
|
||||
return;
|
||||
}
|
||||
this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
|
||||
} else if (
|
||||
usernameMatches.length === 1 &&
|
||||
usernameMatches[0].login.password !== loginInfo.password
|
||||
) {
|
||||
const disabledChangePassword = await this.storageService.get<boolean>(
|
||||
ConstantsService.disableChangedPasswordNotificationKey
|
||||
);
|
||||
if (disabledChangePassword) {
|
||||
return;
|
||||
}
|
||||
this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, loginInfo.password, tab);
|
||||
}
|
||||
}
|
||||
|
||||
await this.createNewCipher(message, folderId);
|
||||
}
|
||||
private async pushAddLoginToQueue(
|
||||
loginDomain: string,
|
||||
loginInfo: AddLoginRuntimeMessage,
|
||||
tab: chrome.tabs.Tab,
|
||||
isVaultLocked: boolean = false
|
||||
) {
|
||||
// remove any old messages for this tab
|
||||
this.removeTabFromNotificationQueue(tab);
|
||||
const message: AddLoginQueueMessage = {
|
||||
type: NotificationQueueMessageType.addLogin,
|
||||
username: loginInfo.username,
|
||||
password: loginInfo.password,
|
||||
domain: loginDomain,
|
||||
uri: loginInfo.url,
|
||||
tabId: tab.id,
|
||||
expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
|
||||
wasVaultLocked: isVaultLocked,
|
||||
};
|
||||
this.notificationQueue.push(message);
|
||||
await this.checkNotificationQueue(tab);
|
||||
}
|
||||
|
||||
private async changedPassword(changeData: ChangePasswordRuntimeMessage, tab: chrome.tabs.Tab) {
|
||||
const loginDomain = Utils.getDomain(changeData.url);
|
||||
if (loginDomain == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let id: string = null;
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
|
||||
if (changeData.currentPassword != null) {
|
||||
const passwordMatches = ciphers.filter(
|
||||
(c) => c.login.password === changeData.currentPassword
|
||||
);
|
||||
if (passwordMatches.length === 1) {
|
||||
id = passwordMatches[0].id;
|
||||
}
|
||||
} else if (ciphers.length === 1) {
|
||||
id = ciphers[0].id;
|
||||
}
|
||||
if (id != null) {
|
||||
this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab);
|
||||
}
|
||||
}
|
||||
|
||||
private async pushChangePasswordToQueue(
|
||||
cipherId: string,
|
||||
loginDomain: string,
|
||||
newPassword: string,
|
||||
tab: chrome.tabs.Tab,
|
||||
isVaultLocked: boolean = false
|
||||
) {
|
||||
// remove any old messages for this tab
|
||||
this.removeTabFromNotificationQueue(tab);
|
||||
const message: AddChangePasswordQueueMessage = {
|
||||
type: NotificationQueueMessageType.changePassword,
|
||||
cipherId: cipherId,
|
||||
newPassword: newPassword,
|
||||
domain: loginDomain,
|
||||
tabId: tab.id,
|
||||
expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
|
||||
wasVaultLocked: isVaultLocked,
|
||||
};
|
||||
this.notificationQueue.push(message);
|
||||
await this.checkNotificationQueue(tab);
|
||||
}
|
||||
|
||||
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
const queueMessage = this.notificationQueue[i];
|
||||
if (
|
||||
queueMessage.tabId !== tab.id ||
|
||||
(queueMessage.type !== NotificationQueueMessageType.addLogin &&
|
||||
queueMessage.type !== NotificationQueueMessageType.changePassword)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tabDomain = Utils.getDomain(tab.url);
|
||||
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.notificationQueue.splice(i, 1);
|
||||
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
|
||||
|
||||
if (queueMessage.type === NotificationQueueMessageType.changePassword) {
|
||||
const message = queueMessage as AddChangePasswordQueueMessage;
|
||||
const cipher = await this.getDecryptedCipherById(message.cipherId);
|
||||
if (cipher == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this.updateCipher(cipher, message.newPassword);
|
||||
return;
|
||||
}
|
||||
|
||||
private async createNewCipher(queueMessage: AddLoginQueueMessage, folderId: string) {
|
||||
const loginModel = new LoginView();
|
||||
const loginUri = new LoginUriView();
|
||||
loginUri.uri = queueMessage.uri;
|
||||
loginModel.uris = [loginUri];
|
||||
loginModel.username = queueMessage.username;
|
||||
loginModel.password = queueMessage.password;
|
||||
const model = new CipherView();
|
||||
model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain;
|
||||
model.name = model.name.replace(/^www\./, '');
|
||||
model.type = CipherType.Login;
|
||||
model.login = loginModel;
|
||||
if (!queueMessage.wasVaultLocked) {
|
||||
await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId);
|
||||
}
|
||||
|
||||
if (!Utils.isNullOrWhitespace(folderId)) {
|
||||
const folders = await this.folderService.getAllDecrypted();
|
||||
if (folders.some(x => x.id === folderId)) {
|
||||
model.folderId = folderId;
|
||||
}
|
||||
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||
if (
|
||||
queueMessage.type === NotificationQueueMessageType.addLogin &&
|
||||
queueMessage.wasVaultLocked === true
|
||||
) {
|
||||
const message = queueMessage as AddLoginQueueMessage;
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri);
|
||||
const usernameMatches = ciphers.filter(
|
||||
(c) => c.login.username != null && c.login.username.toLowerCase() === message.username
|
||||
);
|
||||
|
||||
if (usernameMatches.length >= 1) {
|
||||
await this.updateCipher(usernameMatches[0], message.password);
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = await this.cipherService.encrypt(model);
|
||||
await this.cipherService.saveWithServer(cipher);
|
||||
await this.createNewCipher(message, folderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async createNewCipher(queueMessage: AddLoginQueueMessage, folderId: string) {
|
||||
const loginModel = new LoginView();
|
||||
const loginUri = new LoginUriView();
|
||||
loginUri.uri = queueMessage.uri;
|
||||
loginModel.uris = [loginUri];
|
||||
loginModel.username = queueMessage.username;
|
||||
loginModel.password = queueMessage.password;
|
||||
const model = new CipherView();
|
||||
model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain;
|
||||
model.name = model.name.replace(/^www\./, "");
|
||||
model.type = CipherType.Login;
|
||||
model.login = loginModel;
|
||||
|
||||
if (!Utils.isNullOrWhitespace(folderId)) {
|
||||
const folders = await this.folderService.getAllDecrypted();
|
||||
if (folders.some((x) => x.id === folderId)) {
|
||||
model.folderId = folderId;
|
||||
}
|
||||
}
|
||||
|
||||
private async getDecryptedCipherById(cipherId: string) {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
if (cipher != null && cipher.type === CipherType.Login) {
|
||||
return await cipher.decrypt();
|
||||
}
|
||||
return null;
|
||||
const cipher = await this.cipherService.encrypt(model);
|
||||
await this.cipherService.saveWithServer(cipher);
|
||||
}
|
||||
|
||||
private async getDecryptedCipherById(cipherId: string) {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
if (cipher != null && cipher.type === CipherType.Login) {
|
||||
return await cipher.decrypt();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async updateCipher(cipher: CipherView, newPassword: string) {
|
||||
if (cipher != null && cipher.type === CipherType.Login) {
|
||||
cipher.login.password = newPassword;
|
||||
const newCipher = await this.cipherService.encrypt(cipher);
|
||||
await this.cipherService.saveWithServer(newCipher);
|
||||
}
|
||||
}
|
||||
|
||||
private async saveNever(tab: chrome.tabs.Tab) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
const queueMessage = this.notificationQueue[i];
|
||||
if (
|
||||
queueMessage.tabId !== tab.id ||
|
||||
queueMessage.type !== NotificationQueueMessageType.addLogin
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tabDomain = Utils.getDomain(tab.url);
|
||||
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.notificationQueue.splice(i, 1);
|
||||
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
|
||||
|
||||
const hostname = Utils.getHostname(tab.url);
|
||||
await this.cipherService.saveNeverDomain(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
|
||||
const responseData: any = {};
|
||||
if (responseCommand === "notificationBarGetFoldersList") {
|
||||
responseData.folders = await this.folderService.getAllDecrypted();
|
||||
}
|
||||
|
||||
private async updateCipher(cipher: CipherView, newPassword: string) {
|
||||
if (cipher != null && cipher.type === CipherType.Login) {
|
||||
cipher.login.password = newPassword;
|
||||
const newCipher = await this.cipherService.encrypt(cipher);
|
||||
await this.cipherService.saveWithServer(newCipher);
|
||||
}
|
||||
}
|
||||
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
|
||||
}
|
||||
|
||||
private async saveNever(tab: chrome.tabs.Tab) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
const queueMessage = this.notificationQueue[i];
|
||||
if (queueMessage.tabId !== tab.id || queueMessage.type !== NotificationQueueMessageType.addLogin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tabDomain = Utils.getDomain(tab.url);
|
||||
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.notificationQueue.splice(i, 1);
|
||||
BrowserApi.tabSendMessageData(tab, 'closeNotificationBar');
|
||||
|
||||
const hostname = Utils.getHostname(tab.url);
|
||||
await this.cipherService.saveNeverDomain(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
|
||||
const responseData: any = {};
|
||||
if (responseCommand === 'notificationBarGetFoldersList') {
|
||||
responseData.folders = await this.folderService.getAllDecrypted();
|
||||
}
|
||||
|
||||
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
|
||||
}
|
||||
|
||||
private async allowPersonalOwnership(): Promise<boolean> {
|
||||
return !await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
}
|
||||
private async allowPersonalOwnership(): Promise<boolean> {
|
||||
return !(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,219 +1,248 @@
|
||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { SystemService } from 'jslib-common/abstractions/system.service';
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
|
||||
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||
import { SystemService } from "jslib-common/abstractions/system.service";
|
||||
import { ConstantsService } from "jslib-common/services/constants.service";
|
||||
|
||||
import { AutofillService } from '../services/abstractions/autofill.service';
|
||||
import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service';
|
||||
import { AutofillService } from "../services/abstractions/autofill.service";
|
||||
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
||||
|
||||
import { BrowserApi } from '../browser/browserApi';
|
||||
import { BrowserApi } from "../browser/browserApi";
|
||||
|
||||
import MainBackground from './main.background';
|
||||
import MainBackground from "./main.background";
|
||||
|
||||
import { Utils } from 'jslib-common/misc/utils';
|
||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||
|
||||
export default class RuntimeBackground {
|
||||
private autofillTimeout: any;
|
||||
private pageDetailsToAutoFill: any[] = [];
|
||||
private onInstalledReason: string = null;
|
||||
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
|
||||
private autofillTimeout: any;
|
||||
private pageDetailsToAutoFill: any[] = [];
|
||||
private onInstalledReason: string = null;
|
||||
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
|
||||
|
||||
constructor(private main: MainBackground, private autofillService: AutofillService,
|
||||
private platformUtilsService: BrowserPlatformUtilsService,
|
||||
private storageService: StorageService, private i18nService: I18nService,
|
||||
private notificationsService: NotificationsService, private systemService: SystemService,
|
||||
private environmentService: EnvironmentService, private messagingService: MessagingService,
|
||||
private logService: LogService) {
|
||||
constructor(
|
||||
private main: MainBackground,
|
||||
private autofillService: AutofillService,
|
||||
private platformUtilsService: BrowserPlatformUtilsService,
|
||||
private storageService: StorageService,
|
||||
private i18nService: I18nService,
|
||||
private notificationsService: NotificationsService,
|
||||
private systemService: SystemService,
|
||||
private environmentService: EnvironmentService,
|
||||
private messagingService: MessagingService,
|
||||
private logService: LogService
|
||||
) {
|
||||
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
||||
chrome.runtime.onInstalled.addListener((details: any) => {
|
||||
this.onInstalledReason = details.reason;
|
||||
});
|
||||
}
|
||||
|
||||
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
||||
chrome.runtime.onInstalled.addListener((details: any) => {
|
||||
this.onInstalledReason = details.reason;
|
||||
});
|
||||
async init() {
|
||||
if (!chrome.runtime) {
|
||||
return;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!chrome.runtime) {
|
||||
return;
|
||||
await this.checkOnInstalled();
|
||||
BrowserApi.messageListener(
|
||||
"runtime.background",
|
||||
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||
await this.processMessage(msg, sender, sendResponse);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async processMessage(msg: any, sender: any, sendResponse: any) {
|
||||
switch (msg.command) {
|
||||
case "loggedIn":
|
||||
case "unlocked":
|
||||
let item: LockedVaultPendingNotificationsItem;
|
||||
|
||||
if (this.lockedVaultPendingNotifications.length > 0) {
|
||||
await BrowserApi.closeLoginTab();
|
||||
|
||||
item = this.lockedVaultPendingNotifications.pop();
|
||||
if (item.commandToRetry.sender?.tab?.id) {
|
||||
await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id);
|
||||
}
|
||||
}
|
||||
|
||||
await this.checkOnInstalled();
|
||||
BrowserApi.messageListener('runtime.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||
await this.processMessage(msg, sender, sendResponse);
|
||||
});
|
||||
}
|
||||
await this.main.setIcon();
|
||||
await this.main.refreshBadgeAndMenu(false);
|
||||
this.notificationsService.updateConnection(msg.command === "unlocked");
|
||||
this.systemService.cancelProcessReload();
|
||||
|
||||
async processMessage(msg: any, sender: any, sendResponse: any) {
|
||||
switch (msg.command) {
|
||||
case 'loggedIn':
|
||||
case 'unlocked':
|
||||
let item: LockedVaultPendingNotificationsItem;
|
||||
|
||||
if (this.lockedVaultPendingNotifications.length > 0) {
|
||||
await BrowserApi.closeLoginTab();
|
||||
|
||||
item = this.lockedVaultPendingNotifications.pop();
|
||||
if (item.commandToRetry.sender?.tab?.id) {
|
||||
await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id);
|
||||
}
|
||||
}
|
||||
|
||||
await this.main.setIcon();
|
||||
await this.main.refreshBadgeAndMenu(false);
|
||||
this.notificationsService.updateConnection(msg.command === 'unlocked');
|
||||
this.systemService.cancelProcessReload();
|
||||
|
||||
if (item) {
|
||||
await BrowserApi.tabSendMessageData(item.commandToRetry.sender.tab, 'unlockCompleted', item);
|
||||
}
|
||||
break;
|
||||
case 'addToLockedVaultPendingNotifications':
|
||||
this.lockedVaultPendingNotifications.push(msg.data);
|
||||
break;
|
||||
case 'logout':
|
||||
await this.main.logout(msg.expired);
|
||||
break;
|
||||
case 'syncCompleted':
|
||||
if (msg.successfully) {
|
||||
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
|
||||
}
|
||||
break;
|
||||
case 'openPopup':
|
||||
await this.main.openPopup();
|
||||
break;
|
||||
case 'promptForLogin':
|
||||
await BrowserApi.createNewTab('popup/index.html?uilocation=popout', true, true);
|
||||
break;
|
||||
case 'showDialogResolve':
|
||||
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
|
||||
break;
|
||||
case 'bgCollectPageDetails':
|
||||
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
|
||||
break;
|
||||
case 'bgUpdateContextMenu':
|
||||
case 'editedCipher':
|
||||
case 'addedCipher':
|
||||
case 'deletedCipher':
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
break;
|
||||
case 'bgReseedStorage':
|
||||
await this.main.reseedStorage();
|
||||
break;
|
||||
case 'collectPageDetailsResponse':
|
||||
switch (msg.sender) {
|
||||
case 'autofiller':
|
||||
case 'autofill_cmd':
|
||||
const totpCode = await this.autofillService.doAutoFillActiveTab([{
|
||||
frameId: sender.frameId,
|
||||
tab: msg.tab,
|
||||
details: msg.details,
|
||||
}], msg.sender === 'autofill_cmd');
|
||||
if (totpCode != null) {
|
||||
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
||||
}
|
||||
break;
|
||||
case 'contextMenu':
|
||||
clearTimeout(this.autofillTimeout);
|
||||
this.pageDetailsToAutoFill.push({
|
||||
frameId: sender.frameId,
|
||||
tab: msg.tab,
|
||||
details: msg.details,
|
||||
});
|
||||
this.autofillTimeout = setTimeout(async () => await this.autofillPage(), 300);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'authResult':
|
||||
const vaultUrl = this.environmentService.getWebVaultUrl();
|
||||
|
||||
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
BrowserApi.createNewTab('popup/index.html?uilocation=popout#/sso?code=' +
|
||||
encodeURIComponent(msg.code) + '&state=' + encodeURIComponent(msg.state));
|
||||
}
|
||||
catch {
|
||||
this.logService.error('Unable to open sso popout tab');
|
||||
}
|
||||
break;
|
||||
case 'webAuthnResult':
|
||||
const vaultUrl2 = this.environmentService.getWebVaultUrl();
|
||||
|
||||
if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = `webAuthnResponse=${encodeURIComponent(msg.data)};` +
|
||||
`remember=${encodeURIComponent(msg.remember)}`;
|
||||
BrowserApi.createNewTab(`popup/index.html?uilocation=popout#/2fa;${params}`, undefined, false);
|
||||
break;
|
||||
case 'reloadPopup':
|
||||
this.messagingService.send('reloadPopup');
|
||||
break;
|
||||
case 'emailVerificationRequired':
|
||||
this.messagingService.send('showDialog', {
|
||||
dialogId: 'emailVerificationRequired',
|
||||
title: this.i18nService.t('emailVerificationRequired'),
|
||||
text: this.i18nService.t('emailVerificationRequiredDesc'),
|
||||
confirmText: this.i18nService.t('ok'),
|
||||
type: 'info',
|
||||
});
|
||||
break;
|
||||
case 'getClickedElementResponse':
|
||||
this.platformUtilsService.copyToClipboard(msg.identifier, { window: window });
|
||||
default:
|
||||
break;
|
||||
if (item) {
|
||||
await BrowserApi.tabSendMessageData(
|
||||
item.commandToRetry.sender.tab,
|
||||
"unlockCompleted",
|
||||
item
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async autofillPage() {
|
||||
const totpCode = await this.autofillService.doAutoFill({
|
||||
cipher: this.main.loginToAutoFill,
|
||||
pageDetails: this.pageDetailsToAutoFill,
|
||||
fillNewPassword: true,
|
||||
});
|
||||
|
||||
if (totpCode != null) {
|
||||
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
||||
break;
|
||||
case "addToLockedVaultPendingNotifications":
|
||||
this.lockedVaultPendingNotifications.push(msg.data);
|
||||
break;
|
||||
case "logout":
|
||||
await this.main.logout(msg.expired);
|
||||
break;
|
||||
case "syncCompleted":
|
||||
if (msg.successfully) {
|
||||
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
|
||||
}
|
||||
|
||||
// reset
|
||||
this.main.loginToAutoFill = null;
|
||||
this.pageDetailsToAutoFill = [];
|
||||
}
|
||||
|
||||
private async checkOnInstalled() {
|
||||
setTimeout(async () => {
|
||||
if (this.onInstalledReason != null) {
|
||||
if (this.onInstalledReason === 'install') {
|
||||
BrowserApi.createNewTab('https://bitwarden.com/browser-start/');
|
||||
await this.setDefaultSettings();
|
||||
}
|
||||
|
||||
this.onInstalledReason = null;
|
||||
break;
|
||||
case "openPopup":
|
||||
await this.main.openPopup();
|
||||
break;
|
||||
case "promptForLogin":
|
||||
await BrowserApi.createNewTab("popup/index.html?uilocation=popout", true, true);
|
||||
break;
|
||||
case "showDialogResolve":
|
||||
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
|
||||
break;
|
||||
case "bgCollectPageDetails":
|
||||
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
|
||||
break;
|
||||
case "bgUpdateContextMenu":
|
||||
case "editedCipher":
|
||||
case "addedCipher":
|
||||
case "deletedCipher":
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
break;
|
||||
case "bgReseedStorage":
|
||||
await this.main.reseedStorage();
|
||||
break;
|
||||
case "collectPageDetailsResponse":
|
||||
switch (msg.sender) {
|
||||
case "autofiller":
|
||||
case "autofill_cmd":
|
||||
const totpCode = await this.autofillService.doAutoFillActiveTab(
|
||||
[
|
||||
{
|
||||
frameId: sender.frameId,
|
||||
tab: msg.tab,
|
||||
details: msg.details,
|
||||
},
|
||||
],
|
||||
msg.sender === "autofill_cmd"
|
||||
);
|
||||
if (totpCode != null) {
|
||||
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
break;
|
||||
case "contextMenu":
|
||||
clearTimeout(this.autofillTimeout);
|
||||
this.pageDetailsToAutoFill.push({
|
||||
frameId: sender.frameId,
|
||||
tab: msg.tab,
|
||||
details: msg.details,
|
||||
});
|
||||
this.autofillTimeout = setTimeout(async () => await this.autofillPage(), 300);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "authResult":
|
||||
const vaultUrl = this.environmentService.getWebVaultUrl();
|
||||
|
||||
private async setDefaultSettings() {
|
||||
// Default timeout option to "on restart".
|
||||
const currentVaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
||||
if (currentVaultTimeout == null) {
|
||||
await this.storageService.save(ConstantsService.vaultTimeoutKey, -1);
|
||||
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Default action to "lock".
|
||||
const currentVaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
||||
if (currentVaultTimeoutAction == null) {
|
||||
await this.storageService.save(ConstantsService.vaultTimeoutActionKey, 'lock');
|
||||
try {
|
||||
BrowserApi.createNewTab(
|
||||
"popup/index.html?uilocation=popout#/sso?code=" +
|
||||
encodeURIComponent(msg.code) +
|
||||
"&state=" +
|
||||
encodeURIComponent(msg.state)
|
||||
);
|
||||
} catch {
|
||||
this.logService.error("Unable to open sso popout tab");
|
||||
}
|
||||
break;
|
||||
case "webAuthnResult":
|
||||
const vaultUrl2 = this.environmentService.getWebVaultUrl();
|
||||
|
||||
if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params =
|
||||
`webAuthnResponse=${encodeURIComponent(msg.data)};` +
|
||||
`remember=${encodeURIComponent(msg.remember)}`;
|
||||
BrowserApi.createNewTab(
|
||||
`popup/index.html?uilocation=popout#/2fa;${params}`,
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case "reloadPopup":
|
||||
this.messagingService.send("reloadPopup");
|
||||
break;
|
||||
case "emailVerificationRequired":
|
||||
this.messagingService.send("showDialog", {
|
||||
dialogId: "emailVerificationRequired",
|
||||
title: this.i18nService.t("emailVerificationRequired"),
|
||||
text: this.i18nService.t("emailVerificationRequiredDesc"),
|
||||
confirmText: this.i18nService.t("ok"),
|
||||
type: "info",
|
||||
});
|
||||
break;
|
||||
case "getClickedElementResponse":
|
||||
this.platformUtilsService.copyToClipboard(msg.identifier, { window: window });
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async autofillPage() {
|
||||
const totpCode = await this.autofillService.doAutoFill({
|
||||
cipher: this.main.loginToAutoFill,
|
||||
pageDetails: this.pageDetailsToAutoFill,
|
||||
fillNewPassword: true,
|
||||
});
|
||||
|
||||
if (totpCode != null) {
|
||||
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
||||
}
|
||||
|
||||
// reset
|
||||
this.main.loginToAutoFill = null;
|
||||
this.pageDetailsToAutoFill = [];
|
||||
}
|
||||
|
||||
private async checkOnInstalled() {
|
||||
setTimeout(async () => {
|
||||
if (this.onInstalledReason != null) {
|
||||
if (this.onInstalledReason === "install") {
|
||||
BrowserApi.createNewTab("https://bitwarden.com/browser-start/");
|
||||
await this.setDefaultSettings();
|
||||
}
|
||||
|
||||
this.onInstalledReason = null;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private async setDefaultSettings() {
|
||||
// Default timeout option to "on restart".
|
||||
const currentVaultTimeout = await this.storageService.get<number>(
|
||||
ConstantsService.vaultTimeoutKey
|
||||
);
|
||||
if (currentVaultTimeout == null) {
|
||||
await this.storageService.save(ConstantsService.vaultTimeoutKey, -1);
|
||||
}
|
||||
|
||||
// Default action to "lock".
|
||||
const currentVaultTimeoutAction = await this.storageService.get<string>(
|
||||
ConstantsService.vaultTimeoutActionKey
|
||||
);
|
||||
if (currentVaultTimeoutAction == null) {
|
||||
await this.storageService.save(ConstantsService.vaultTimeoutActionKey, "lock");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,45 @@
|
||||
import MainBackground from './main.background';
|
||||
import NotificationBackground from './notification.background';
|
||||
import MainBackground from "./main.background";
|
||||
import NotificationBackground from "./notification.background";
|
||||
|
||||
export default class TabsBackground {
|
||||
constructor(private main: MainBackground, private notificationBackground: NotificationBackground) {
|
||||
constructor(
|
||||
private main: MainBackground,
|
||||
private notificationBackground: NotificationBackground
|
||||
) {}
|
||||
|
||||
async init() {
|
||||
if (!chrome.tabs) {
|
||||
return;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!chrome.tabs) {
|
||||
return;
|
||||
chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => {
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
this.main.messagingService.send("tabActivated");
|
||||
this.main.messagingService.send("tabChanged");
|
||||
});
|
||||
|
||||
chrome.tabs.onReplaced.addListener(async (addedTabId: number, removedTabId: number) => {
|
||||
if (this.main.onReplacedRan) {
|
||||
return;
|
||||
}
|
||||
this.main.onReplacedRan = true;
|
||||
await this.notificationBackground.checkNotificationQueue();
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
this.main.messagingService.send("tabReplaced");
|
||||
this.main.messagingService.send("tabChanged");
|
||||
});
|
||||
|
||||
chrome.tabs.onUpdated.addListener(
|
||||
async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => {
|
||||
if (this.main.onUpdatedRan) {
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => {
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
this.main.messagingService.send('tabActivated');
|
||||
this.main.messagingService.send('tabChanged');
|
||||
});
|
||||
|
||||
chrome.tabs.onReplaced.addListener(async (addedTabId: number, removedTabId: number) => {
|
||||
if (this.main.onReplacedRan) {
|
||||
return;
|
||||
}
|
||||
this.main.onReplacedRan = true;
|
||||
await this.notificationBackground.checkNotificationQueue();
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
this.main.messagingService.send('tabReplaced');
|
||||
this.main.messagingService.send('tabChanged');
|
||||
});
|
||||
|
||||
chrome.tabs.onUpdated.addListener(async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => {
|
||||
if (this.main.onUpdatedRan) {
|
||||
return;
|
||||
}
|
||||
this.main.onUpdatedRan = true;
|
||||
await this.notificationBackground.checkNotificationQueue(tab);
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
this.main.messagingService.send('tabUpdated');
|
||||
this.main.messagingService.send('tabChanged');
|
||||
});
|
||||
}
|
||||
this.main.onUpdatedRan = true;
|
||||
await this.notificationBackground.checkNotificationQueue(tab);
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
this.main.messagingService.send("tabUpdated");
|
||||
this.main.messagingService.send("tabChanged");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,94 @@
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
|
||||
import { UriMatchType } from 'jslib-common/enums/uriMatchType';
|
||||
import { UriMatchType } from "jslib-common/enums/uriMatchType";
|
||||
|
||||
export default class WebRequestBackground {
|
||||
private pendingAuthRequests: any[] = [];
|
||||
private webRequest: any;
|
||||
private isFirefox: boolean;
|
||||
private pendingAuthRequests: any[] = [];
|
||||
private webRequest: any;
|
||||
private isFirefox: boolean;
|
||||
|
||||
constructor(platformUtilsService: PlatformUtilsService, private cipherService: CipherService,
|
||||
private vaultTimeoutService: VaultTimeoutService) {
|
||||
this.webRequest = (window as any).chrome.webRequest;
|
||||
this.isFirefox = platformUtilsService.isFirefox();
|
||||
constructor(
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
private cipherService: CipherService,
|
||||
private vaultTimeoutService: VaultTimeoutService
|
||||
) {
|
||||
this.webRequest = (window as any).chrome.webRequest;
|
||||
this.isFirefox = platformUtilsService.isFirefox();
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.webRequest || !this.webRequest.onAuthRequired) {
|
||||
return;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.webRequest || !this.webRequest.onAuthRequired) {
|
||||
return;
|
||||
this.webRequest.onAuthRequired.addListener(
|
||||
async (details: any, callback: any) => {
|
||||
if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.webRequest.onAuthRequired.addListener(async (details: any, callback: any) => {
|
||||
if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.pendingAuthRequests.push(details.requestId);
|
||||
|
||||
this.pendingAuthRequests.push(details.requestId);
|
||||
if (this.isFirefox) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await this.resolveAuthCredentials(details.url, resolve, reject);
|
||||
});
|
||||
} else {
|
||||
await this.resolveAuthCredentials(details.url, callback, callback);
|
||||
}
|
||||
},
|
||||
{ urls: ["http://*/*", "https://*/*"] },
|
||||
[this.isFirefox ? "blocking" : "asyncBlocking"]
|
||||
);
|
||||
|
||||
if (this.isFirefox) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await this.resolveAuthCredentials(details.url, resolve, reject);
|
||||
});
|
||||
} else {
|
||||
await this.resolveAuthCredentials(details.url, callback, callback);
|
||||
}
|
||||
}, { urls: ['http://*/*', 'https://*/*'] }, [this.isFirefox ? 'blocking' : 'asyncBlocking']);
|
||||
this.webRequest.onCompleted.addListener((details: any) => this.completeAuthRequest(details), {
|
||||
urls: ["http://*/*"],
|
||||
});
|
||||
this.webRequest.onErrorOccurred.addListener(
|
||||
(details: any) => this.completeAuthRequest(details),
|
||||
{
|
||||
urls: ["http://*/*"],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.webRequest.onCompleted.addListener(
|
||||
(details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] });
|
||||
this.webRequest.onErrorOccurred.addListener(
|
||||
(details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] });
|
||||
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
|
||||
if (await this.vaultTimeoutService.isLocked()) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||
domain,
|
||||
null,
|
||||
UriMatchType.Host
|
||||
);
|
||||
if (ciphers == null || ciphers.length !== 1) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(domain, null, UriMatchType.Host);
|
||||
if (ciphers == null || ciphers.length !== 1) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
success({
|
||||
authCredentials: {
|
||||
username: ciphers[0].login.username,
|
||||
password: ciphers[0].login.password,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
error();
|
||||
}
|
||||
success({
|
||||
authCredentials: {
|
||||
username: ciphers[0].login.username,
|
||||
password: ciphers[0].login.password,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
error();
|
||||
}
|
||||
}
|
||||
|
||||
private completeAuthRequest(details: any) {
|
||||
const i = this.pendingAuthRequests.indexOf(details.requestId);
|
||||
if (i > -1) {
|
||||
this.pendingAuthRequests.splice(i, 1);
|
||||
}
|
||||
private completeAuthRequest(details: any) {
|
||||
const i = this.pendingAuthRequests.indexOf(details.requestId);
|
||||
if (i > -1) {
|
||||
this.pendingAuthRequests.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import MainBackground from './main.background';
|
||||
import MainBackground from "./main.background";
|
||||
|
||||
export default class WindowsBackground {
|
||||
private windows: any;
|
||||
private windows: any;
|
||||
|
||||
constructor(private main: MainBackground) {
|
||||
this.windows = chrome.windows;
|
||||
constructor(private main: MainBackground) {
|
||||
this.windows = chrome.windows;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.windows) {
|
||||
return;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.windows) {
|
||||
return;
|
||||
}
|
||||
this.windows.onFocusChanged.addListener(async (windowId: any) => {
|
||||
if (windowId === null || windowId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.windows.onFocusChanged.addListener(async (windowId: any) => {
|
||||
if (windowId === null || windowId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
this.main.messagingService.send('windowFocused');
|
||||
this.main.messagingService.send('windowChanged');
|
||||
});
|
||||
}
|
||||
await this.main.refreshBadgeAndMenu();
|
||||
this.main.messagingService.send("windowFocused");
|
||||
this.main.messagingService.send("windowChanged");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user