mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
* Passkey stuff Co-authored-by: Anders Åberg <github@andersaberg.com> * Ugly hacks * Work On Modal State Management * Applying modalStyles * modal * Improved hide/show * fixed promise * File name * fix prettier * Protecting against null API's and undefined data * Only show fake popup to devs * cleanup mock code * rename minmimal-app to modal-app * Added comment * Added comment * removed old comment * Avoided changing minimum size * Add small comment * Rename component * adress feedback * Fixed uppercase file * Fixed build * Added codeowners * added void * commentary * feat: reset setting on app start * Moved reset to be in main / process launch * Add comment to create window * Added a little bit of styling * Use Messaging service to loadUrl * Enable passkeysautofill * Add logging * halfbaked * Integration working * And now it works without extra delay * Clean up * add note about messaging * lb * removed console.logs * Cleanup and adress review feedback * This hides the swift UI * pick credential, draft * Remove logger * a whole lot of wiring * not working * Improved wiring * Cancel after 90s * Introduced observable * Launching bitwarden if its not running * Passing position from native to electron * Rename inModalMode to modalMode * remove tap * revert spaces * added back isDev * cleaned up a bit * Cleanup swift file * tweaked logging * clean up * Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Update apps/desktop/src/platform/main/autofill/native-autofill.main.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Update apps/desktop/src/platform/services/desktop-settings.service.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * adress position feedback * Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Removed extra logging * Adjusted error logging * Use .error to log errors * remove dead code * Update desktop-autofill.service.ts * use parseCredentialId instead of guidToRawFormat * Update apps/desktop/src/autofill/services/desktop-autofill.service.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Change windowXy to a Record instead of [number,number] * Update apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Remove unsued dep and comment * changed timeout to be spec recommended maxium, 10 minutes, for now. * Correctly assume UP * Removed extra cancelRequest in deinint * Add timeout and UV to confirmChoseCipher UV is performed by UI, not the service * Improved docs regarding undefined cipherId * cleanup: UP is no longer undefined * Run completeError if ipc messages conversion failed * don't throw, instead return undefined * Disabled passkey provider * Throw error if no activeUserId was found * removed comment * Fixed lint * removed unsued service * reset entitlement formatting * Update entitlements.mas.plist --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Colton Hurst <colton@coltonhurst.com> Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com> Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
222 lines
6.9 KiB
TypeScript
222 lines
6.9 KiB
TypeScript
// FIXME: Update this file to be type safe and remove this and next line
|
|
// @ts-strict-ignore
|
|
import * as path from "path";
|
|
|
|
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray } from "electron";
|
|
import { firstValueFrom } from "rxjs";
|
|
|
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
import { BiometricsService } from "@bitwarden/key-management";
|
|
|
|
import { DesktopSettingsService } from "../platform/services/desktop-settings.service";
|
|
import { isDev } from "../utils";
|
|
|
|
import { WindowMain } from "./window.main";
|
|
|
|
export class TrayMain {
|
|
contextMenu: Menu;
|
|
|
|
private appName: string;
|
|
private tray: Tray;
|
|
private icon: string | Electron.NativeImage;
|
|
private pressedIcon: Electron.NativeImage;
|
|
|
|
constructor(
|
|
private windowMain: WindowMain,
|
|
private i18nService: I18nService,
|
|
private desktopSettingsService: DesktopSettingsService,
|
|
private messagingService: MessagingService,
|
|
private biometricService: BiometricsService,
|
|
) {
|
|
if (process.platform === "win32") {
|
|
this.icon = path.join(__dirname, "/images/icon.ico");
|
|
} else if (process.platform === "darwin") {
|
|
const nImage = nativeImage.createFromPath(path.join(__dirname, "/images/icon-template.png"));
|
|
nImage.setTemplateImage(true);
|
|
this.icon = nImage;
|
|
this.pressedIcon = nativeImage.createFromPath(
|
|
path.join(__dirname, "/images/icon-highlight.png"),
|
|
);
|
|
} else {
|
|
this.icon = path.join(__dirname, "/images/icon.png");
|
|
}
|
|
}
|
|
|
|
async init(appName: string, additionalMenuItems: MenuItemConstructorOptions[] = null) {
|
|
this.appName = appName;
|
|
|
|
const menuItemOptions: MenuItemConstructorOptions[] = [
|
|
{
|
|
label: this.i18nService.t("showHide"),
|
|
click: () => this.toggleWindow(),
|
|
},
|
|
{
|
|
visible: isDev(),
|
|
label: "Fake Popup",
|
|
click: () => this.fakePopup(),
|
|
},
|
|
{ type: "separator" },
|
|
{
|
|
label: this.i18nService.t("exit"),
|
|
click: () => this.closeWindow(),
|
|
},
|
|
];
|
|
|
|
if (additionalMenuItems != null) {
|
|
menuItemOptions.splice(1, 0, ...additionalMenuItems);
|
|
}
|
|
|
|
this.contextMenu = Menu.buildFromTemplate(menuItemOptions);
|
|
if (await firstValueFrom(this.desktopSettingsService.trayEnabled$)) {
|
|
this.showTray();
|
|
}
|
|
}
|
|
|
|
setupWindowListeners(win: BrowserWindow) {
|
|
win.on("minimize", async () => {
|
|
if (await firstValueFrom(this.desktopSettingsService.minimizeToTray$)) {
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
this.hideToTray();
|
|
}
|
|
});
|
|
|
|
win.on("restore", async () => {
|
|
await this.biometricService.setShouldAutopromptNow(true);
|
|
});
|
|
|
|
win.on("close", async (e: Event) => {
|
|
if (await firstValueFrom(this.desktopSettingsService.closeToTray$)) {
|
|
if (!this.windowMain.isQuitting) {
|
|
e.preventDefault();
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
this.hideToTray();
|
|
}
|
|
}
|
|
});
|
|
|
|
win.on("show", async () => {
|
|
const enableTray = await firstValueFrom(this.desktopSettingsService.trayEnabled$);
|
|
if (!enableTray) {
|
|
setTimeout(() => this.removeTray(false), 100);
|
|
}
|
|
});
|
|
}
|
|
|
|
removeTray(showWindow = true) {
|
|
// Due to https://github.com/electron/electron/issues/17622
|
|
// we cannot destroy the tray icon on linux.
|
|
if (this.tray != null && process.platform !== "linux") {
|
|
this.tray.destroy();
|
|
this.tray = null;
|
|
}
|
|
|
|
if (showWindow && this.windowMain.win != null && !this.windowMain.win.isVisible()) {
|
|
this.windowMain.win.show();
|
|
}
|
|
}
|
|
|
|
async hideToTray() {
|
|
this.showTray();
|
|
if (this.windowMain.win != null) {
|
|
this.windowMain.win.hide();
|
|
}
|
|
if (this.isDarwin() && !(await firstValueFrom(this.desktopSettingsService.alwaysShowDock$))) {
|
|
this.hideDock();
|
|
}
|
|
}
|
|
|
|
restoreFromTray() {
|
|
if (this.windowMain.win == null || !this.windowMain.win.isVisible()) {
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
this.toggleWindow();
|
|
}
|
|
}
|
|
|
|
showTray() {
|
|
if (this.tray != null) {
|
|
return;
|
|
}
|
|
|
|
this.tray = new Tray(this.icon);
|
|
this.tray.setToolTip(this.appName);
|
|
this.tray.on("click", () => this.toggleWindow());
|
|
this.tray.on("right-click", () => this.tray.popUpContextMenu(this.contextMenu));
|
|
|
|
if (this.pressedIcon != null) {
|
|
this.tray.setPressedImage(this.pressedIcon);
|
|
}
|
|
if (this.contextMenu != null && !this.isDarwin()) {
|
|
this.tray.setContextMenu(this.contextMenu);
|
|
}
|
|
}
|
|
|
|
updateContextMenu() {
|
|
if (this.tray != null && this.contextMenu != null && this.isLinux()) {
|
|
this.tray.setContextMenu(this.contextMenu);
|
|
}
|
|
}
|
|
|
|
private hideDock() {
|
|
app.dock.hide();
|
|
}
|
|
|
|
private showDock() {
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
app.dock.show();
|
|
}
|
|
|
|
private isDarwin() {
|
|
return process.platform === "darwin";
|
|
}
|
|
|
|
private isLinux() {
|
|
return process.platform === "linux";
|
|
}
|
|
|
|
private async toggleWindow() {
|
|
if (this.windowMain.win == null) {
|
|
if (this.isDarwin()) {
|
|
// On MacOS, closing the window via the red button destroys the BrowserWindow instance.
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
this.windowMain.createWindow().then(() => {
|
|
this.windowMain.win.show();
|
|
this.showDock();
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
if (this.windowMain.win.isVisible()) {
|
|
this.windowMain.win.hide();
|
|
if (this.isDarwin() && !(await firstValueFrom(this.desktopSettingsService.alwaysShowDock$))) {
|
|
this.hideDock();
|
|
}
|
|
} else {
|
|
this.windowMain.show();
|
|
if (this.isDarwin()) {
|
|
this.showDock();
|
|
}
|
|
}
|
|
}
|
|
|
|
private closeWindow() {
|
|
this.windowMain.isQuitting = true;
|
|
if (this.windowMain.win != null) {
|
|
this.windowMain.win.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method is used to test modal behavior during development and could be removed in the future.
|
|
* @returns
|
|
*/
|
|
private async fakePopup() {
|
|
await this.messagingService.send("loadurl", { url: "/passkeys", modal: true });
|
|
}
|
|
}
|