diff --git a/apps/desktop/resources/passkeys.png b/apps/desktop/resources/passkeys.png new file mode 100644 index 00000000000..ca6513e5ec3 Binary files /dev/null and b/apps/desktop/resources/passkeys.png differ diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index f5023cb4249..3c25d5f9995 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -52,6 +52,7 @@ import { TwoFactorComponent } from "../auth/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { VaultComponent } from "../vault/app/vault/vault.component"; +import { PasskeysComponent } from "./components/passkeys.component"; import { SendComponent } from "./tools/send/send.component"; /** @@ -200,6 +201,10 @@ const routes: Routes = [ ], }, ), + { + path: "passkeys", + component: PasskeysComponent, + }, { path: "", component: AnonLayoutWrapperComponent, diff --git a/apps/desktop/src/app/components/passkeys.component.ts b/apps/desktop/src/app/components/passkeys.component.ts new file mode 100644 index 00000000000..dcfd2bd5a45 --- /dev/null +++ b/apps/desktop/src/app/components/passkeys.component.ts @@ -0,0 +1,22 @@ +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; + +export type BrowserSyncVerificationDialogParams = { + fingerprint: string[]; +}; + +@Component({ + standalone: true, + template: ` + + `, + imports: [JslibModule], +}) +export class PasskeysComponent {} diff --git a/apps/desktop/src/main/tray.main.ts b/apps/desktop/src/main/tray.main.ts index 8450a653222..7656cd8922d 100644 --- a/apps/desktop/src/main/tray.main.ts +++ b/apps/desktop/src/main/tray.main.ts @@ -1,4 +1,5 @@ import * as path from "path"; +import * as url from "url"; import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray } from "electron"; import { firstValueFrom } from "rxjs"; @@ -6,6 +7,7 @@ import { firstValueFrom } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; +import { cleanUserAgent } from "../utils"; import { WindowMain } from "./window.main"; @@ -44,6 +46,10 @@ export class TrayMain { label: this.i18nService.t("showHide"), click: () => this.toggleWindow(), }, + { + label: "Fake Popup", + click: () => this.fakePopup(), + }, { type: "separator" }, { label: this.i18nService.t("exit"), @@ -195,4 +201,47 @@ export class TrayMain { this.windowMain.win.close(); } } + + private async fakePopup() { + if (this.windowMain.win == null || this.windowMain.win.isDestroyed()) { + await this.windowMain.createWindow("minimal-app"); + return; + } + + // Restyle existing + const existingWin = this.windowMain.win; + + existingWin.setBounds({ + width: 400, + height: 600, + }); + existingWin.setSize(400, 600, true); + existingWin.setWindowButtonVisibility(false); + existingWin.resizable = false; + await existingWin.loadURL( + url.format({ + protocol: "file:", + //pathname: `${__dirname}/index.html`, + pathname: path.join(__dirname, "/index.html"), + slashes: true, + hash: "/passkeys", + query: { + redirectUrl: "/passkeys", + }, + }), + { + userAgent: cleanUserAgent(existingWin.webContents.userAgent), + }, + ); + existingWin.center(); + existingWin.setAlwaysOnTop(true); + existingWin.show(); + // TODO: Do things + // ?? Enqueue the browser location + // Change browser location and styling to minimal + + // Show popup + // Change styling back to full + // ?? Dequeue browser location + } } diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 276a2bdc979..f12e9826515 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -168,39 +168,66 @@ export class WindowMain { }); } - async createWindow(): Promise { - this.windowStates[mainWindowSizeKey] = await this.getWindowState( - this.defaultWidth, - this.defaultHeight, - ); - this.enableAlwaysOnTop = await firstValueFrom(this.desktopSettingsService.alwaysOnTop$); + async createWindow(template: "full-app" | "minimal-app" = "full-app"): Promise { + if (template === "full-app") { + this.windowStates[mainWindowSizeKey] = await this.getWindowState( + this.defaultWidth, + this.defaultHeight, + ); + this.enableAlwaysOnTop = await firstValueFrom(this.desktopSettingsService.alwaysOnTop$); - this.session = session.fromPartition("persist:bitwarden", { cache: false }); + this.session = session.fromPartition("persist:bitwarden", { cache: false }); - // Create the browser window. - this.win = new BrowserWindow({ - width: this.windowStates[mainWindowSizeKey].width, - height: this.windowStates[mainWindowSizeKey].height, - minWidth: 680, - minHeight: 500, - x: this.windowStates[mainWindowSizeKey].x, - y: this.windowStates[mainWindowSizeKey].y, - title: app.name, - icon: isLinux() ? path.join(__dirname, "/images/icon.png") : undefined, - titleBarStyle: isMac() ? "hiddenInset" : undefined, - show: false, - backgroundColor: await this.getBackgroundColor(), - alwaysOnTop: this.enableAlwaysOnTop, - webPreferences: { - preload: path.join(__dirname, "preload.js"), - spellcheck: false, - nodeIntegration: false, - backgroundThrottling: false, - contextIsolation: true, - session: this.session, - devTools: isDev(), - }, - }); + // Create the browser window. + this.win = new BrowserWindow({ + width: this.windowStates[mainWindowSizeKey].width, + height: this.windowStates[mainWindowSizeKey].height, + minWidth: 680, + minHeight: 500, + x: this.windowStates[mainWindowSizeKey].x, + y: this.windowStates[mainWindowSizeKey].y, + title: app.name, + icon: isLinux() ? path.join(__dirname, "/images/icon.png") : undefined, + titleBarStyle: isMac() ? "hiddenInset" : undefined, + show: false, + backgroundColor: await this.getBackgroundColor(), + alwaysOnTop: this.enableAlwaysOnTop, + webPreferences: { + preload: path.join(__dirname, "preload.js"), + spellcheck: false, + nodeIntegration: false, + backgroundThrottling: false, + contextIsolation: true, + session: this.session, + devTools: isDev(), + }, + }); + } else { + // + this.win = new BrowserWindow({ + width: 400, + height: 600, + resizable: false, + icon: null, + center: true, + titleBarStyle: "hiddenInset", + frame: false, + alwaysOnTop: true, + backgroundColor: await this.getBackgroundColor(), + show: true, + webPreferences: { + preload: path.join(__dirname, "preload.js"), + spellcheck: false, + nodeIntegration: false, + backgroundThrottling: false, + contextIsolation: true, + session: this.session, + devTools: isDev(), + }, + }); + + this.win.setWindowButtonVisibility(false); + } this.win.webContents.on("dom-ready", () => { this.win.webContents.zoomFactor = this.windowStates[mainWindowSizeKey].zoomFactor ?? 1.0; @@ -213,19 +240,37 @@ export class WindowMain { // Show it later since it might need to be maximized. this.win.show(); - // and load the index.html of the app. - // 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.win.loadURL( - url.format({ - protocol: "file:", - pathname: path.join(__dirname, "/index.html"), - slashes: true, - }), - { - userAgent: cleanUserAgent(this.win.webContents.userAgent), - }, - ); + if (template === "full-app") { + // and load the index.html of the app. + // 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.win.loadURL( + url.format({ + protocol: "file:", + pathname: path.join(__dirname, "/index.html"), + slashes: true, + }), + { + userAgent: cleanUserAgent(this.win.webContents.userAgent), + }, + ); + } else { + await this.win.loadURL( + url.format({ + protocol: "file:", + //pathname: `${__dirname}/index.html`, + pathname: path.join(__dirname, "/index.html"), + slashes: true, + hash: "/passkeys", + query: { + redirectUrl: "/passkeys", + }, + }), + { + userAgent: cleanUserAgent(this.win.webContents.userAgent), + }, + ); + } // Open the DevTools. if (isDev()) {