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()) {