Ps 1291 fix extension icon updates (#3571)

* Add needed factories for AuthService

WIP: Allow console logs

* Add badge updates

* Init by listener

* Improve tab identification

* Define MV3 background init

* Init services in factories.

Requires conversion of all factories to promises.

We need to initialize in factory since the requester of a service
doesn't necessarily know all dependencies for that service. The only
alternative is to create an out parameter for a
generated init function, which isn't ideal.

* Improve badge setting

* Use `update-badge` in mv2 and mv3

Separates menu and badge updates

* Use update-badge everywhere

* Use BrowserApi where possible

* Update factories

* Merge duplicated methods

* Continue using private mode messager for now

* Add static platform determination.

* Break down methods and extract BrowserApi Concerns

* Prefer strict equals

* Init two-factor service in factory

* Use globalThis types

* Prefer `globalThis`

* Use Window type definition updated with Opera

Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com>

* Distinguish Opera from Safari

Opera includes Gecko, Chrome, Safari, and Opera in its user agent. We need to make sure that
we're not in Opera prior to testing Safari.

* Update import

* Initialize search-service for update badge context

* Build all browser MV3 artifacts

only uploading Chrome, Edge and Opera artifacts for now, as those support manifest V3

Also corrects build artifact to lower case.

* Remove individual dist

Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com>
Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
Matt Gibson 2022-10-19 09:55:57 -04:00 committed by GitHub
parent 23d4dcd839
commit b4ac5a8bef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 424 additions and 149 deletions

View File

@ -1,17 +1,31 @@
import MainBackground from "./background/main.background";
import { BrowserApi } from "./browser/browserApi";
import { ClearClipboard } from "./clipboard";
import { onCommandListener } from "./listeners/onCommandListener";
import { onInstallListener } from "./listeners/onInstallListener";
import { UpdateBadge } from "./listeners/update-badge";
const manifestV3MessageListeners: ((
serviceCache: Record<string, unknown>,
message: { command: string }
) => void | Promise<void>)[] = [UpdateBadge.messageListener];
type AlarmAction = (executionTime: Date, serviceCache: Record<string, unknown>) => void;
const AlarmActions: AlarmAction[] = [ClearClipboard.run];
const manifest = chrome.runtime.getManifest();
if (manifest.manifest_version === 3) {
if (BrowserApi.manifestVersion === 3) {
chrome.commands.onCommand.addListener(onCommandListener);
chrome.runtime.onInstalled.addListener(onInstallListener);
chrome.tabs.onActivated.addListener(UpdateBadge.tabsOnActivatedListener);
chrome.tabs.onReplaced.addListener(UpdateBadge.tabsOnReplacedListener);
chrome.tabs.onUpdated.addListener(UpdateBadge.tabsOnUpdatedListener);
BrowserApi.messageListener("runtime.background", (message) => {
const serviceCache = {};
manifestV3MessageListeners.forEach((listener) => {
listener(serviceCache, message);
});
});
chrome.alarms.onAlarm.addListener((_alarm) => {
const executionTime = new Date();
const serviceCache = {};

View File

@ -82,6 +82,7 @@ import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFu
import { BrowserApi } from "../browser/browserApi";
import { SafariApp } from "../browser/safariApp";
import { UpdateBadge } from "../listeners/update-badge";
import { Account } from "../models/account";
import { PopupUtilsService } from "../popup/services/popup-utils.service";
import { AutofillService as AutofillServiceAbstraction } from "../services/abstractions/autofill.service";
@ -183,15 +184,18 @@ export default class MainBackground {
private syncTimeout: any;
private isSafari: boolean;
private nativeMessagingBackground: NativeMessagingBackground;
popupOnlyContext: boolean;
constructor(public isPrivateMode: boolean = false) {
this.popupOnlyContext = isPrivateMode || BrowserApi.manifestVersion === 3;
// Services
const lockedCallback = async (userId?: string) => {
if (this.notificationsService != null) {
this.notificationsService.updateConnection(false);
}
await this.setIcon();
await this.refreshBadgeAndMenu(true);
await this.refreshBadge();
await this.refreshMenu(true);
if (this.systemService != null) {
await this.systemService.clearPendingClipboard();
await this.systemService.startProcessReload(this.authService);
@ -201,7 +205,7 @@ export default class MainBackground {
const logoutCallback = async (expired: boolean, userId?: string) =>
await this.logout(expired, userId);
this.messagingService = isPrivateMode
this.messagingService = this.popupOnlyContext
? new BrowserMessagingPrivateModeBackgroundService()
: new BrowserMessagingService();
this.logService = new ConsoleLogService(false);
@ -209,7 +213,7 @@ export default class MainBackground {
this.storageService = new BrowserLocalStorageService();
this.secureStorageService = new BrowserLocalStorageService();
this.memoryStorageService =
chrome.runtime.getManifest().manifest_version == 3
BrowserApi.manifestVersion === 3
? new LocalBackedSessionStorageService(
new EncryptService(this.cryptoFunctionService, this.logService, false),
new KeyGenerationService(this.cryptoFunctionService)
@ -562,14 +566,12 @@ export default class MainBackground {
// Set Private Mode windows to the default icon - they do not share state with the background page
const privateWindows = await BrowserApi.getPrivateModeWindows();
privateWindows.forEach(async (win) => {
await this.actionSetIcon(chrome.browserAction, "", win.id);
await this.actionSetIcon(this.sidebarAction, "", win.id);
await new UpdateBadge(self).setBadgeIcon("", win.id);
});
BrowserApi.onWindowCreated(async (win) => {
if (win.incognito) {
await this.actionSetIcon(chrome.browserAction, "", win.id);
await this.actionSetIcon(this.sidebarAction, "", win.id);
await new UpdateBadge(self).setBadgeIcon("", win.id);
}
});
}
@ -577,7 +579,7 @@ export default class MainBackground {
return new Promise<void>((resolve) => {
setTimeout(async () => {
await this.environmentService.setUrlsFromStorage();
await this.setIcon();
await this.refreshBadge();
this.fullSync(true);
setTimeout(() => this.notificationsService.init(), 2500);
resolve();
@ -585,25 +587,11 @@ export default class MainBackground {
});
}
async setIcon() {
if ((!chrome.browserAction && !this.sidebarAction) || this.isPrivateMode) {
return;
}
const authStatus = await this.authService.getAuthStatus();
let suffix = "";
if (authStatus === AuthenticationStatus.LoggedOut) {
suffix = "_gray";
} else if (authStatus === AuthenticationStatus.Locked) {
suffix = "_locked";
}
await this.actionSetIcon(chrome.browserAction, suffix);
await this.actionSetIcon(this.sidebarAction, suffix);
async refreshBadge() {
await new UpdateBadge(self).run({ existingServices: this as any });
}
async refreshBadgeAndMenu(forLocked = false) {
async refreshMenu(forLocked = false) {
if (!chrome.windows || !chrome.contextMenus) {
return;
}
@ -616,7 +604,7 @@ export default class MainBackground {
}
if (forLocked) {
await this.loadMenuAndUpdateBadgeForNoAccessState(!menuDisabled);
await this.loadMenuForNoAccessState(!menuDisabled);
this.onUpdatedRan = this.onReplacedRan = false;
return;
}
@ -652,8 +640,11 @@ export default class MainBackground {
this.messagingService.send("doneLoggingOut", { expired: expired, userId: userId });
}
await this.setIcon();
await this.refreshBadgeAndMenu(true);
if (BrowserApi.manifestVersion === 3) {
BrowserApi.sendMessage("updateBadge");
}
await this.refreshBadge();
await this.refreshMenu(true);
await this.reseedStorage();
this.notificationsService.updateConnection(false);
await this.systemService.clearPendingClipboard();
@ -801,18 +792,15 @@ export default class MainBackground {
}
private async contextMenuReady(tab: any, contextMenuEnabled: boolean) {
await this.loadMenuAndUpdateBadge(tab.url, tab.id, contextMenuEnabled);
await this.loadMenu(tab.url, tab.id, contextMenuEnabled);
this.onUpdatedRan = this.onReplacedRan = false;
}
private async loadMenuAndUpdateBadge(url: string, tabId: number, contextMenuEnabled: boolean) {
private async loadMenu(url: string, tabId: number, contextMenuEnabled: boolean) {
if (!url || (!chrome.browserAction && !this.sidebarAction)) {
return;
}
this.actionSetBadgeBackgroundColor(chrome.browserAction);
this.actionSetBadgeBackgroundColor(this.sidebarAction);
this.menuOptionsLoaded = [];
const authStatus = await this.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.Unlocked) {
@ -826,50 +814,26 @@ export default class MainBackground {
});
}
const disableBadgeCounter = await this.stateService.getDisableBadgeCounter();
let theText = "";
if (!disableBadgeCounter) {
if (ciphers.length > 0 && ciphers.length <= 9) {
theText = ciphers.length.toString();
} else if (ciphers.length > 0) {
theText = "9+";
}
}
if (contextMenuEnabled && ciphers.length === 0) {
await this.loadNoLoginsContextMenuOptions(this.i18nService.t("noMatchingLogins"));
}
this.sidebarActionSetBadgeText(theText, tabId);
this.browserActionSetBadgeText(theText, tabId);
return;
} catch (e) {
this.logService.error(e);
}
}
await this.loadMenuAndUpdateBadgeForNoAccessState(contextMenuEnabled);
await this.loadMenuForNoAccessState(contextMenuEnabled);
}
private async loadMenuAndUpdateBadgeForNoAccessState(contextMenuEnabled: boolean) {
private async loadMenuForNoAccessState(contextMenuEnabled: boolean) {
if (contextMenuEnabled) {
const authed = await this.stateService.getIsAuthenticated();
await this.loadNoLoginsContextMenuOptions(
this.i18nService.t(authed ? "unlockVaultMenu" : "loginToVaultMenu")
);
}
const tabs = await BrowserApi.getActiveTabs();
if (tabs != null) {
tabs.forEach((tab) => {
if (tab.id != null) {
this.browserActionSetBadgeText("", tab.id);
this.sidebarActionSetBadgeText("", tab.id);
}
});
}
}
private async loadLoginContextMenuOptions(cipher: any) {
@ -1026,42 +990,4 @@ export default class MainBackground {
});
}
}
private actionSetBadgeBackgroundColor(action: any) {
if (action && action.setBadgeBackgroundColor) {
action.setBadgeBackgroundColor({ color: "#294e5f" });
}
}
private browserActionSetBadgeText(text: string, tabId: number) {
if (chrome.browserAction && chrome.browserAction.setBadgeText) {
chrome.browserAction.setBadgeText({
text: text,
tabId: tabId,
});
}
}
private sidebarActionSetBadgeText(text: string, tabId: number) {
if (!this.sidebarAction) {
return;
}
if (this.sidebarAction.setBadgeText) {
this.sidebarAction.setBadgeText({
text: text,
tabId: tabId,
});
} else if (this.sidebarAction.setTitle) {
let title = "Bitwarden";
if (text && text !== "") {
title += " [" + text + "]";
}
this.sidebarAction.setTitle({
title: title,
tabId: tabId,
});
}
}
}

View File

@ -51,7 +51,7 @@ export default class RuntimeBackground {
};
BrowserApi.messageListener("runtime.background", backgroundMessageListener);
if (this.main.isPrivateMode) {
if (this.main.popupOnlyContext) {
(window as any).bitwardenBackgroundMessageListener = backgroundMessageListener;
}
}
@ -71,8 +71,8 @@ export default class RuntimeBackground {
}
}
await this.main.setIcon();
await this.main.refreshBadgeAndMenu(false);
await this.main.refreshBadge();
await this.main.refreshMenu(false);
this.notificationsService.updateConnection(msg.command === "unlocked");
this.systemService.cancelProcessReload();
@ -93,7 +93,10 @@ export default class RuntimeBackground {
break;
case "syncCompleted":
if (msg.successfully) {
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
setTimeout(async () => {
await this.main.refreshBadge();
await this.main.refreshMenu();
}, 2000);
}
break;
case "openPopup":
@ -112,7 +115,8 @@ export default class RuntimeBackground {
case "editedCipher":
case "addedCipher":
case "deletedCipher":
await this.main.refreshBadgeAndMenu();
await this.main.refreshBadge();
await this.main.refreshMenu();
break;
case "bgReseedStorage":
await this.main.reseedStorage();

View File

@ -5,7 +5,7 @@ import { CachedServices, factory, FactoryOptions } from "./factory-options";
type CryptoFunctionServiceFactoryOptions = FactoryOptions & {
cryptoFunctionServiceOptions: {
win: Window | typeof global;
win: Window | typeof globalThis;
};
};

View File

@ -1,6 +1,7 @@
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
import { BrowserApi } from "../../browser/browserApi";
import BrowserLocalStorageService from "../../services/browserLocalStorage.service";
import { LocalBackedSessionStorageService } from "../../services/localBackedSessionStorage.service";
@ -38,7 +39,7 @@ export function memoryStorageServiceFactory(
opts: MemoryStorageServiceInitOptions
): Promise<AbstractStorageService> {
return factory(cache, "memoryStorageService", opts, async () => {
if (chrome.runtime.getManifest().manifest_version == 3) {
if (BrowserApi.manifestVersion === 3) {
return new LocalBackedSessionStorageService(
await encryptServiceFactory(cache, opts),
await keyGenerationServiceFactory(cache, opts)

View File

@ -28,6 +28,6 @@ export async function twoFactorServiceFactory(
await platformUtilsServiceFactory(cache, opts)
)
);
await service.init();
service.init();
return service;
}

View File

@ -24,7 +24,8 @@ export default class TabsBackground {
});
chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => {
await this.main.refreshBadgeAndMenu();
await this.main.refreshBadge();
await this.main.refreshMenu();
this.main.messagingService.send("tabChanged");
});
@ -35,7 +36,8 @@ export default class TabsBackground {
this.main.onReplacedRan = true;
await this.notificationBackground.checkNotificationQueue();
await this.main.refreshBadgeAndMenu();
await this.main.refreshBadge();
await this.main.refreshMenu();
this.main.messagingService.send("tabChanged");
});
@ -55,7 +57,8 @@ export default class TabsBackground {
this.main.onUpdatedRan = true;
await this.notificationBackground.checkNotificationQueue(tab);
await this.main.refreshBadgeAndMenu();
await this.main.refreshBadge();
await this.main.refreshMenu();
this.main.messagingService.send("tabChanged");
}
);

View File

@ -4,6 +4,8 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
import { BrowserApi } from "../browser/browserApi";
export default class WebRequestBackground {
private pendingAuthRequests: any[] = [];
private webRequest: any;
@ -14,8 +16,7 @@ export default class WebRequestBackground {
private cipherService: CipherService,
private authService: AuthService
) {
const manifest = chrome.runtime.getManifest();
if (manifest.manifest_version === 2) {
if (BrowserApi.manifestVersion === 2) {
this.webRequest = (window as any).chrome.webRequest;
}
this.isFirefox = platformUtilsService.isFirefox();

View File

@ -1,3 +1,4 @@
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
import { TabMessage } from "../types/tab-messages";
export class BrowserApi {
@ -10,6 +11,10 @@ export class BrowserApi {
static isFirefoxOnAndroid: boolean =
navigator.userAgent.indexOf("Firefox/") !== -1 && navigator.userAgent.indexOf("Android") !== -1;
static get manifestVersion() {
return chrome.runtime.getManifest().manifest_version;
}
static async getTabFromCurrentWindowId(): Promise<chrome.tabs.Tab> | null {
return await BrowserApi.tabsQueryFirst({
active: true,
@ -17,6 +22,13 @@ export class BrowserApi {
});
}
static async getTab(tabId: number) {
if (tabId == null) {
return null;
}
return await chrome.tabs.get(tabId);
}
static async getTabFromCurrentWindow(): Promise<chrome.tabs.Tab> | null {
return await BrowserApi.tabsQueryFirst({
active: true,
@ -211,4 +223,16 @@ export class BrowserApi {
chrome.runtime.getPlatformInfo(resolve);
});
}
static getBrowserAction() {
return BrowserApi.manifestVersion === 3 ? chrome.action : chrome.browserAction;
}
static getSidebarAction(win: Window & typeof globalThis) {
return BrowserPlatformUtilsService.isSafari(win)
? null
: typeof win.opr !== "undefined" && win.opr.sidebarAction
? win.opr.sidebarAction
: win.chrome.sidebarAction;
}
}

View File

@ -29,7 +29,7 @@ export class SessionSyncer {
}
init() {
if (chrome.runtime.getManifest().manifest_version != 3) {
if (BrowserApi.manifestVersion !== 3) {
return;
}

View File

@ -100,28 +100,28 @@ type OperaSidebarAction = {
onBlur: OperaEvent<Window>;
};
/**
* This is for firefox's sidebar action and it is based on the opera one but with a few less methods
*
* @link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/sidebarAction
*/
type FirefoxSidebarAction = Omit<
OperaSidebarAction,
| "setBadgeText"
| "getBadgeText"
| "setBadgeBackgroundColor"
| "getBadgeBackgroundColor"
| "onFocus"
| "onBlur"
>;
type Opera = {
addons: OperaAddons;
sidebarAction: OperaSidebarAction;
};
declare namespace chrome {
/**
* This is for firefoxes sidebar action and it is based on the opera one but with a few less methods
*
* @link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/sidebarAction
*/
let sidebarAction:
| Omit<
OperaSidebarAction,
| "setBadgeText"
| "getBadgeText"
| "setBadgeBackgroundColor"
| "getBadgeBackgroundColor"
| "onFocus"
| "onBlur"
>
| undefined;
let sidebarAction: FirefoxSidebarAction | undefined;
}
interface Window {

View File

@ -0,0 +1,276 @@
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { Utils } from "@bitwarden/common/misc/utils";
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
import { ContainerService } from "@bitwarden/common/services/container.service";
import IconDetails from "../background/models/iconDetails";
import { authServiceFactory } from "../background/service_factories/auth-service.factory";
import { cipherServiceFactory } from "../background/service_factories/cipher-service.factory";
import { searchServiceFactory } from "../background/service_factories/search-service.factory";
import { stateServiceFactory } from "../background/service_factories/state-service.factory";
import { BrowserApi } from "../browser/browserApi";
import { Account } from "../models/account";
import { StateService } from "../services/abstractions/state.service";
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
export type BadgeOptions = {
tab?: chrome.tabs.Tab;
windowId?: number;
};
export class UpdateBadge {
private authService: AuthService;
private stateService: StateService;
private cipherService: CipherService;
private badgeAction: typeof chrome.action;
private sidebarAction: OperaSidebarAction | FirefoxSidebarAction;
private inited = false;
private win: Window & typeof globalThis;
private static readonly listenedToCommands = [
"updateBadge",
"loggedIn",
"unlocked",
"syncCompleted",
"bgUpdateContextMenu",
"editedCipher",
"addedCipher",
"deletedCipher",
];
static async tabsOnActivatedListener(activeInfo: chrome.tabs.TabActiveInfo) {
await new UpdateBadge(self).run({ tabId: activeInfo.tabId });
}
static async tabsOnReplacedListener(addedTabId: number, removedTabId: number) {
await new UpdateBadge(self).run({ tabId: addedTabId });
}
static async tabsOnUpdatedListener(tabId: number) {
await new UpdateBadge(self).run({ tabId });
}
static async messageListener(
serviceCache: Record<string, unknown>,
message: { command: string; tabId: number }
) {
if (!UpdateBadge.listenedToCommands.includes(message.command)) {
return;
}
await new UpdateBadge(self).run();
}
constructor(win: Window & typeof globalThis) {
this.badgeAction = BrowserApi.getBrowserAction();
this.sidebarAction = BrowserApi.getSidebarAction(self);
this.win = win;
}
async run(opts?: {
tabId?: number;
windowId?: number;
existingServices?: Record<string, unknown>;
}): Promise<void> {
await this.initServices(opts?.existingServices);
const authStatus = await this.authService.getAuthStatus();
const tab = await this.getTab(opts?.tabId, opts?.windowId);
const windowId = tab?.windowId;
await this.setBadgeBackgroundColor();
switch (authStatus) {
case AuthenticationStatus.LoggedOut: {
await this.setLoggedOut({ tab, windowId });
break;
}
case AuthenticationStatus.Locked: {
await this.setLocked({ tab, windowId });
break;
}
case AuthenticationStatus.Unlocked: {
await this.setUnlocked({ tab, windowId });
break;
}
}
}
async setLoggedOut(opts: BadgeOptions): Promise<void> {
await this.setBadgeIcon("_gray", opts?.windowId);
await this.setBadgeText("", opts?.tab?.id);
}
async setLocked(opts: BadgeOptions) {
await this.setBadgeIcon("_locked", opts?.windowId);
await this.setBadgeText("", opts?.tab?.id);
}
async setUnlocked(opts: BadgeOptions) {
await this.initServices();
await this.setBadgeIcon("", opts?.windowId);
const disableBadgeCounter = await this.stateService.getDisableBadgeCounter();
if (disableBadgeCounter) {
return;
}
const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url);
let countText = ciphers.length == 0 ? "" : ciphers.length.toString();
if (ciphers.length > 9) {
countText = "9+";
}
await this.setBadgeText(countText, opts?.tab?.id);
}
setBadgeBackgroundColor(color = "#294e5f") {
if (this.badgeAction?.setBadgeBackgroundColor) {
this.badgeAction.setBadgeBackgroundColor({ color });
}
if (this.isOperaSidebar(this.sidebarAction)) {
this.sidebarAction.setBadgeBackgroundColor({ color });
}
}
setBadgeText(text: string, tabId?: number) {
this.setActionText(text, tabId);
this.setSideBarText(text, tabId);
}
async setBadgeIcon(iconSuffix: string, windowId?: number) {
const options: IconDetails = {
path: {
19: "/images/icon19" + iconSuffix + ".png",
38: "/images/icon38" + iconSuffix + ".png",
},
};
if (BrowserPlatformUtilsService.isFirefox()) {
options.windowId = windowId;
}
await this.setActionIcon(options);
await this.setSidebarActionIcon(options);
}
private setActionText(text: string, tabId?: number) {
if (this.badgeAction?.setBadgeText) {
this.badgeAction.setBadgeText({ text, tabId });
}
}
private setSideBarText(text: string, tabId?: number) {
if (this.isOperaSidebar(this.sidebarAction)) {
this.sidebarAction.setBadgeText({ text, tabId });
} else if (this.sidebarAction) {
// Firefox
const title = `Bitwarden${Utils.isNullOrEmpty(text) ? "" : ` [${text}]`}`;
this.sidebarAction.setTitle({ title, tabId });
}
}
private async setActionIcon(options: IconDetails) {
if (!this.badgeAction?.setIcon) {
return;
}
if (this.useSyncApiCalls) {
this.badgeAction.setIcon(options);
} else {
await new Promise<void>((resolve) => this.badgeAction.setIcon(options, () => resolve()));
}
}
private async setSidebarActionIcon(options: IconDetails) {
if (!this.sidebarAction?.setIcon) {
return;
}
if (this.useSyncApiCalls) {
this.sidebarAction.setIcon(options);
} else {
await new Promise<void>((resolve) => this.sidebarAction.setIcon(options, () => resolve()));
}
}
private async getTab(tabId?: number, windowId?: number) {
return (
(await BrowserApi.getTab(tabId)) ??
(await BrowserApi.tabsQueryFirst({ active: true, windowId })) ??
(await BrowserApi.tabsQueryFirst({ active: true, lastFocusedWindow: true })) ??
(await BrowserApi.tabsQueryFirst({ active: true }))
);
}
private get useSyncApiCalls() {
return (
BrowserPlatformUtilsService.isFirefox() || BrowserPlatformUtilsService.isSafari(this.win)
);
}
private async initServices(existingServiceCache?: Record<string, unknown>): Promise<UpdateBadge> {
if (this.inited) {
return this;
}
const serviceCache: Record<string, unknown> = existingServiceCache || {};
const opts = {
cryptoFunctionServiceOptions: { win: self },
encryptServiceOptions: { logMacFailures: false },
logServiceOptions: { isDev: false },
platformUtilsServiceOptions: {
clipboardWriteCallback: (clipboardValue: string, clearMs: number) =>
Promise.reject("not implemented"),
biometricCallback: () => Promise.reject("not implemented"),
win: self,
},
stateServiceOptions: {
stateFactory: new StateFactory(GlobalState, Account),
},
stateMigrationServiceOptions: {
stateFactory: new StateFactory(GlobalState, Account),
},
apiServiceOptions: {
logoutCallback: () => Promise.reject("not implemented"),
},
keyConnectorServiceOptions: {
logoutCallback: () => Promise.reject("not implemented"),
},
i18nServiceOptions: {
systemLanguage: BrowserApi.getUILanguage(self),
},
};
this.stateService = await stateServiceFactory(serviceCache, opts);
this.authService = await authServiceFactory(serviceCache, opts);
const searchService = await searchServiceFactory(serviceCache, opts);
this.cipherService = await cipherServiceFactory(serviceCache, {
...opts,
cipherServiceOptions: { searchServiceFactory: () => searchService },
});
// Needed for cipher decryption
if (!self.bitwardenContainerService) {
new ContainerService(
serviceCache.cryptoService as CryptoService,
serviceCache.encryptService as AbstractEncryptService
).attachToGlobal(self);
}
this.inited = true;
return this;
}
private isOperaSidebar(
action: OperaSidebarAction | FirefoxSidebarAction
): action is OperaSidebarAction {
return action != null && (action as OperaSidebarAction).setBadgeText != null;
}
}

View File

@ -68,13 +68,14 @@ import { PopupSearchService } from "./popup-search.service";
import { PopupUtilsService } from "./popup-utils.service";
import { UnauthGuardService } from "./unauth-guard.service";
const isPrivateMode = BrowserApi.getBackgroundPage() == null;
const mainBackground: MainBackground = isPrivateMode
const needsBackgroundInit = BrowserApi.getBackgroundPage() == null;
const isPrivateMode = needsBackgroundInit && BrowserApi.manifestVersion !== 3;
const mainBackground: MainBackground = needsBackgroundInit
? createLocalBgService()
: BrowserApi.getBackgroundPage().bitwardenMain;
function createLocalBgService() {
const localBgService = new MainBackground(true);
const localBgService = new MainBackground(isPrivateMode);
localBgService.bootstrap();
return localBgService;
}
@ -108,7 +109,7 @@ function getBgService<T>(service: keyof MainBackground) {
{
provide: MessagingService,
useFactory: () => {
return isPrivateMode
return needsBackgroundInit
? new BrowserMessagingPrivateModePopupService()
: new BrowserMessagingService();
},

View File

@ -16,7 +16,7 @@ describe("Browser Utils Service", () => {
let browserPlatformUtilsService: BrowserPlatformUtilsService;
beforeEach(() => {
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null, self);
browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null, window);
});
afterEach(() => {

View File

@ -28,24 +28,17 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
return this.deviceCache;
}
if (
navigator.userAgent.indexOf(" Firefox/") !== -1 ||
navigator.userAgent.indexOf(" Gecko/") !== -1
) {
if (BrowserPlatformUtilsService.isFirefox()) {
this.deviceCache = DeviceType.FirefoxExtension;
} else if (
(!!this.win.opr && !!opr.addons) ||
!!this.win.opera ||
navigator.userAgent.indexOf(" OPR/") >= 0
) {
} else if (BrowserPlatformUtilsService.isOpera(this.win)) {
this.deviceCache = DeviceType.OperaExtension;
} else if (navigator.userAgent.indexOf(" Edg/") !== -1) {
} else if (BrowserPlatformUtilsService.isEdge()) {
this.deviceCache = DeviceType.EdgeExtension;
} else if (navigator.userAgent.indexOf(" Vivaldi/") !== -1) {
} else if (BrowserPlatformUtilsService.isVivaldi()) {
this.deviceCache = DeviceType.VivaldiExtension;
} else if (this.win.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) {
} else if (BrowserPlatformUtilsService.isChrome(this.win)) {
this.deviceCache = DeviceType.ChromeExtension;
} else if (navigator.userAgent.indexOf(" Safari/") !== -1) {
} else if (BrowserPlatformUtilsService.isSafari(this.win)) {
this.deviceCache = DeviceType.SafariExtension;
}
@ -61,26 +54,58 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
return ClientType.Browser;
}
static isFirefox(): boolean {
return (
navigator.userAgent.indexOf(" Firefox/") !== -1 ||
navigator.userAgent.indexOf(" Gecko/") !== -1
);
}
isFirefox(): boolean {
return this.getDevice() === DeviceType.FirefoxExtension;
}
static isChrome(win: Window & typeof globalThis): boolean {
return win.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1;
}
isChrome(): boolean {
return this.getDevice() === DeviceType.ChromeExtension;
}
static isEdge(): boolean {
return navigator.userAgent.indexOf(" Edg/") !== -1;
}
isEdge(): boolean {
return this.getDevice() === DeviceType.EdgeExtension;
}
static isOpera(win: Window & typeof globalThis): boolean {
return (
(!!win.opr && !!win.opr.addons) || !!win.opera || navigator.userAgent.indexOf(" OPR/") >= 0
);
}
isOpera(): boolean {
return this.getDevice() === DeviceType.OperaExtension;
}
static isVivaldi(): boolean {
return navigator.userAgent.indexOf(" Vivaldi/") !== -1;
}
isVivaldi(): boolean {
return this.getDevice() === DeviceType.VivaldiExtension;
}
static isSafari(win: Window & typeof globalThis): boolean {
// Opera masquerades as Safari, so make sure we're not there first
return (
!BrowserPlatformUtilsService.isOpera(win) && navigator.userAgent.indexOf(" Safari/") !== -1
);
}
isSafari(): boolean {
return this.getDevice() === DeviceType.SafariExtension;
}