mirror of
https://github.com/bitwarden/browser
synced 2026-02-17 09:59:41 +00:00
* Turn on passkeys and dev mode
* PM-19138: Add try-catch to desktop-autofill (#13964)
* PM-19424: React to IPC disconnect (#14123)
* React to IPC disconnects
* Minor cleanup
* Update apps/desktop/package.json
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
* Relaxed ordering
---------
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
* Autofill/pm 9034 implement passkey for unlocked accounts (#13826)
* 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
* add modal components
* update modal with correct ciphers and functionality
* add create screen
* pick credential, draft
* Remove logger
* a whole lot of wiring
* not working
* Improved wiring
* Cancel after 90s
* Introduced observable
* update cipher handling
* update to use matchesUri
* 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
* Fix build issues
* Fix import issues
* Update route names to use `fido2`
* Fix being unable to select a passkey
* Fix linting issues
* Followup to fix merge issues and other comments
* Update `userHandle` value
* Add error handling for missing session or other errors
* Remove unused route
* Fix linting issues
* Simplify updateCredential method
* Followup to remove comments and timeouts and handle errors
* Address lint issue by using `takeUntilDestroyed`
* PR Followup for typescript and vault concerns
* Add try block for cipher creation
* Make userId manditory for cipher service
---------
Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Co-authored-by: Anders Åberg <github@andersaberg.com>
Co-authored-by: Anders Åberg <anders@andersaberg.com>
Co-authored-by: Colton Hurst <colton@coltonhurst.com>
Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com>
Co-authored-by: Evan Bassler <evanbassler@Mac.attlocal.net>
Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
* PM-11455: Trigger sync when user enables OS setting (#14127)
* Implemented a SendNativeStatus command
This allows reporting status or asking the electron app to do something.
* fmt
* Update apps/desktop/src/autofill/services/desktop-autofill.service.ts
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
* clean up
* Don't add empty callbacks
* Removed comment
---------
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
* Added support for handling a locked vault
Handle unlocktimeout
* PM-19511: Add support for ExcludedCredentials (#14128)
* works
* Add mapping
* remove the build script
* cleanup
* simplify updatedCipher (#14179)
* Fix base64url decode on MacOS passkeys (#14227)
* Add support for padding in base64url decode
* whitespace
* whitespace
* Autofill/pm 17444 use reprompt (#14004)
* 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
* add modal components
* update modal with correct ciphers and functionality
* add create screen
* pick credential, draft
* Remove logger
* a whole lot of wiring
* not working
* Improved wiring
* Cancel after 90s
* Introduced observable
* update cipher handling
* update to use matchesUri
* 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
* Fix build issues
* Fix import issues
* Update route names to use `fido2`
* Fix being unable to select a passkey
* Fix linting issues
* Added support for handling a locked vault
* Followup to fix merge issues and other comments
* Update `userHandle` value
* Add error handling for missing session or other errors
* Remove unused route
* Fix linting issues
* Simplify updateCredential method
* Add master password reprompt on passkey create
* Followup to remove comments and timeouts and handle errors
* Address lint issue by using `takeUntilDestroyed`
* Add MP prompt to cipher selection
* Change how timeout is handled
* Include `of` from rxjs
* Hide blue header for passkey popouts (#14095)
* Hide blue header for passkey popouts
* Fix issue with test
* Fix ngOnDestroy complaint
* Import OnDestroy correctly
* Only require master password if item requires it
---------
Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Co-authored-by: Anders Åberg <github@andersaberg.com>
Co-authored-by: Anders Åberg <anders@andersaberg.com>
Co-authored-by: Colton Hurst <colton@coltonhurst.com>
Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com>
Co-authored-by: Evan Bassler <evanbassler@Mac.attlocal.net>
Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
* Change modal size to 600x600
* Improve MacOS Syncing
This changes the behaviour to react to logoff, but not to account locks. It also adds better error handling on the native side.
* Improved modalPosition by allowing multiple calls to applyModalStyles
* moved imports to please lint
* Make passkey header stick for select and create (#14357)
* Added local build command
* Exclude credentials using kvc to avoid comilation error in cicd (#14568)
* Fix syntax error
* Don't use kvc
* Enables the autofill extension in mac and mas builds (#14373)
* Enables autofill extension building
* Try use macos-14
* add --break-system-packages for macos14
* revert using build-native
* try add rustup target add x86_64-apple-darwin
* add more rustup target add x86_64-apple-darwin
* try to force sdk version
* Show SDK versions
* USE KVC for excludedCredentials
* added xcodebuild deugging
* Revert "try to force sdk version"
This reverts commit d94f2550ad.
* Use macos-15
* undo merge
* remove macos-15 from cli
* remove macos-15 from browser
---------
Co-authored-by: Anders Åberg <anders@andersaberg.com>
* Improve Autofill IPC reliability (#14358)
* Delay IPC server start
* Better ipc handling
* Rename ready() to listenerReady()
---------
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
* feat: add test and check for too long buffers (#14775)
* Autofill/PM-19511: Overwrite and reprompt (#14288)
* Show items for url that don't have passkey
* Show existing login items in the UI
* Filter available cipher results (#14399)
* Filter available cipher results
* Fix linting issues
* Update logic for eligible ciphers
* Remove unused method to check matching username
* PM-20608 update styling for excludedCredentials (#14444)
* PM-20608 update styling for excludedCredentials
* Have flow correctly move to creation for excluded cipher
* Remove duplicate confirmNeCredential call
* Revert fido2-authenticator changes and move the excluded check
* Create a separate component for excluded cipher view
* Display traffic light MacOS buttons when the vault is locked (#14673)
* Remove unneccessary filter for excludedCiphers
* Remove dead code from the excluded ciphers work
* Remove excludedCipher checks from fido2 create and vault
* Remove excludedCipher remnants from vault and simplify create cipher logic
* Move cipherHasNoOtherPasskeys to shared fido2-utils
* Remove all containsExcludedCipher references
* Use `bufferToString` to convert `userHandle`
---------
Co-authored-by: Jeffrey Holland <jholland@livefront.com>
Co-authored-by: Jeffrey Holland <124393578+jholland-livefront@users.noreply.github.com>
* Move modal files to `autofill` and rename dir to `credentials` (#14757)
* Show existing login items in the UI
* Filter available cipher results (#14399)
* Filter available cipher results
* Fix linting issues
* Update logic for eligible ciphers
* Remove unused method to check matching username
* PM-20608 update styling for excludedCredentials (#14444)
* PM-20608 update styling for excludedCredentials
* Have flow correctly move to creation for excluded cipher
* Remove duplicate confirmNeCredential call
* Revert fido2-authenticator changes and move the excluded check
* Create a separate component for excluded cipher view
* Display traffic light MacOS buttons when the vault is locked (#14673)
* Remove unneccessary filter for excludedCiphers
* Remove dead code from the excluded ciphers work
* Remove excludedCipher checks from fido2 create and vault
* Move modal files to `autofill` and rename dir to `credentials`
* Update merge issues
* Add tests for `cipherHasNoOtherPasskeys` (#14829)
* Adjust spacing to place new login button below other items (#14877)
* Adjust spacing to place new login button below other items
* Add correct design when no credentials available (#14879)
* Autofill/pm 21903 use translations everywhere for passkeys (#14908)
* Adjust spacing to place new login button below other items
* Add correct design when no credentials available
* Add correct design when no credentials available (#14879)
* Remove hardcoded strings and use translations in passkey flow
* Remove duplicate `select` translation
* Autofill/pm 21864 center unlock vault modal (#14867)
* Center the Locked Vault modal when using passkeys
* Revert swift changes and handle offscreen modals
* Remove comments
* Add rustup for cicd to work (#15055)
* Hide credentials that are in the bin (#15034)
* Add tests for passkey components (#15185)
* Add tests for passkey components
* Reuse cipher in chooseCipher tests and simplify mock creation
* Autofill/pm 22821 center vault modal (#15243)
* Center the vault modal for passkeys
* Add comments and fix electron-builder.json
* Set values to Int32 in the ternaries
* Refactor Fido2 Components (#15105)
* Refactor Fido2 Components
* Address error message and missing session
* Address remaining missing session
* Reset modals so subsequent creates work (#15145)
* Fix broken test
* Rename relevantCiphers to displayedCiphers
* Clean up heading settings, errors, and other concerns
* Address missing comments and throw error in try block
* fix type issue for SimpleDialogType
* fix type issue for SimpleDialogType
* Revert new type
* try using as null to satisfy type issue
* Remove use of firstValueFrom in create component
* PM-22476: Show config UI while enabling Bitwarden (#15149)
* Show config ui while enabling Bitwarden
* locals
* Added Localizable strings
* Changed the linebreakmode
* Removed swedish locals
* Add provisioning profile values to electron build (#15412)
* Address BitwardenShield icon issue
* Fix fido2-vault component
* Display the vault modal when selecting Bitwarden... (#15257)
* Passkeys filtering breaks on SSH keys (#15448)
* Display the blue header on the locked vault passkey flow (#15655)
* PM-23848: Use the MacOS UI-friendly API instead (#15650)
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Fix action text and close vault modal (#15634)
* Fix action text and close vault modal
* Fix broken tests
* Update SVG to support dark mode (#15805)
* When a locked vault is unlocked displays correctly (#15612)
* When a locked vault is unlocked displays correctly
* Keep old behavior while checking for recently unlocked vault
* Revert the electron-builder
* Simplify by using a simple redirect when vault unlocked
* Remove single use of `userSelectedCipher`
* Add a guard clause to unlock
* Revert to original spacing
* Add reactive guard to unlock vault
* Fix for passkey picker closing prematurely
* Remove unneeded root navigation in ensureUnlockedVault
* Fix vault not unlocking
* Update broken tests for lock component
* Add missing brace to preload.ts
* Run lint
* Added explainer
* Moved the explainer
* Tidying up readme
* Add feature flag to short-circuit the passkey provider (#16003)
* Add feature flag to short-circuit the passkey provider
* Check FF in renderer instead
* Lint fixes
* PM-22175: Improve launch of app + window positioning (#15658)
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Implement prepareInterfaceToProvideCredential
* Fix launch of app + window pos
* Wait for animation to complete and use proper position
* Wait for animation to complete and use proper position
* Added commentary
* Remove console.log
* Remove call to removed function
---------
Co-authored-by: Jeffrey Holland <jholland@livefront.com>
Co-authored-by: Jeffrey Holland <124393578+jholland-livefront@users.noreply.github.com>
* Update fido2-vault and fido2-service implementations
* Use tailwind-alike classes for new styles
* Add label to biticons in passkey modals
* Fix broken vault test
* Revert to original `isDev` function
* Add comment to lock component describing `disable-redirect` param
* Use tailwind classes instead of custom sticky header class
* Use standard `tw-z-10` for z-index
* Change log service levels
* Mock svg icons for CI
* Add back provisioning profiles
* Remove `--break-system-packages` and simplify commands
* Revert `cipherId` param for `confirmNewCredential`
* Remove placeholder UI
* Small improvements to the readme
* Remove optional userId and deprecated method
* Autofill should own the macos_provider (#16271)
* Autofill should own the macos_provider
* Autofill should own the macos_provider
* Remove unnecessary logs, no magic numbers, revert `cipherId?`
* Fixes for broken build
* Update test issues
* [BEEEP] Use tracing in macOS provider
* Update comments and add null check for ciphers
* Update status comments and readme
* Remove electron modal mode link
* Clarify modal mode use
* Add comment about usernames
* Add comment that we don't support extensions yet
* Added comment about base64 format
* Use NO_CALLBACK_INDICATOR
* cb -> callback
* Update apps/desktop/desktop_native/napi/src/lib.rs
Co-authored-by: neuronull <9162534+neuronull@users.noreply.github.com>
* Clean up Fido2Create subscriptions and update comments
* added comment to clarify silent exception
* Add comments
* clean up unwrap()
* set log level filter to INFO
* Address modal popup issue
* plutil on Info.plist
* Adhere to style guides
* Fix broken lock ui component tests
* Fix broken lock ui component tests
* Added codeowners entry
* logservice.warning -> debug
* Uint8Array -> ArrayBuffer
* Remove autofill entitlement
* Fix linting issues
* Fix arm build issue
* Adjust build command
* Add missing entitlement
* revert missing entitlement change
* Add proper autofill entitlements
* Remove autofill extension from mas builds
* Run rust formatter
---------
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
Co-authored-by: Jeffrey Holland <124393578+jholland-livefront@users.noreply.github.com>
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: Evan Bassler <evanbassler@Mac.attlocal.net>
Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
Co-authored-by: Nathan Ansel <nathan@livefront.com>
Co-authored-by: Jeffrey Holland <jholland@livefront.com>
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
Co-authored-by: neuronull <9162534+neuronull@users.noreply.github.com>
550 lines
19 KiB
TypeScript
550 lines
19 KiB
TypeScript
// FIXME: Update this file to be type safe and remove this and next line
|
|
// @ts-strict-ignore
|
|
import { once } from "node:events";
|
|
import * as path from "path";
|
|
import * as url from "url";
|
|
|
|
import { app, BrowserWindow, ipcMain, nativeTheme, screen, session } from "electron";
|
|
import { concatMap, firstValueFrom, pairwise } from "rxjs";
|
|
|
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
|
import { ThemeTypes, Theme } from "@bitwarden/common/platform/enums";
|
|
import { processisolations } from "@bitwarden/desktop-napi";
|
|
import { BiometricStateService } from "@bitwarden/key-management";
|
|
|
|
import { WindowState } from "../platform/models/domain/window-state";
|
|
import { applyMainWindowStyles, applyPopupModalStyles } from "../platform/popup-modal-styles";
|
|
import { DesktopSettingsService } from "../platform/services/desktop-settings.service";
|
|
import {
|
|
cleanUserAgent,
|
|
isDev,
|
|
isLinux,
|
|
isMac,
|
|
isMacAppStore,
|
|
isSnapStore,
|
|
isWindows,
|
|
} from "../utils";
|
|
|
|
const mainWindowSizeKey = "mainWindowSize";
|
|
const WindowEventHandlingDelay = 100;
|
|
export class WindowMain {
|
|
win: BrowserWindow;
|
|
isQuitting = false;
|
|
isClosing = false;
|
|
|
|
private windowStateChangeTimer: NodeJS.Timeout;
|
|
private windowStates: { [key: string]: WindowState } = {};
|
|
private enableAlwaysOnTop = false;
|
|
private enableRendererProcessForceCrashReload = true;
|
|
session: Electron.Session;
|
|
|
|
readonly defaultWidth = 950;
|
|
readonly defaultHeight = 790;
|
|
|
|
constructor(
|
|
private biometricStateService: BiometricStateService,
|
|
private logService: LogService,
|
|
private storageService: AbstractStorageService,
|
|
private desktopSettingsService: DesktopSettingsService,
|
|
private argvCallback: (argv: string[]) => void = null,
|
|
private createWindowCallback: (win: BrowserWindow) => void,
|
|
) {}
|
|
|
|
init(): Promise<any> {
|
|
// Perform a hard reload of the render process by crashing it. This is suboptimal but ensures that all memory gets
|
|
// cleared, as the process itself will be completely garbage collected.
|
|
ipcMain.on("reload-process", async () => {
|
|
this.logService.info("Reloading render process");
|
|
// User might have changed theme, ensure the window is updated.
|
|
this.win.setBackgroundColor(await this.getBackgroundColor());
|
|
|
|
// By default some linux distro collect core dumps on crashes which gets written to disk.
|
|
if (this.enableRendererProcessForceCrashReload) {
|
|
const crashEvent = once(this.win.webContents, "render-process-gone");
|
|
this.win.webContents.forcefullyCrashRenderer();
|
|
await crashEvent;
|
|
}
|
|
|
|
this.win.webContents.reloadIgnoringCache();
|
|
// 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.session.clearCache();
|
|
this.logService.info("Render process reloaded");
|
|
});
|
|
|
|
ipcMain.on("window-focus", () => {
|
|
if (this.win != null) {
|
|
this.win.show();
|
|
this.win.focus();
|
|
}
|
|
});
|
|
|
|
ipcMain.on("window-hide", () => {
|
|
if (this.win != null) {
|
|
if (isWindows()) {
|
|
// On windows, to return focus we need minimize
|
|
this.win.minimize();
|
|
} else {
|
|
this.win.hide();
|
|
}
|
|
}
|
|
});
|
|
|
|
this.desktopSettingsService.modalMode$
|
|
.pipe(
|
|
pairwise(),
|
|
concatMap(async ([lastValue, newValue]) => {
|
|
if (lastValue.isModalModeActive && !newValue.isModalModeActive) {
|
|
// Reset the window state to the main window state
|
|
applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]);
|
|
// Because modal is used in front of another app, UX wise it makes sense to hide the main window when leaving modal mode.
|
|
this.win.hide();
|
|
} else if (newValue.isModalModeActive) {
|
|
// Apply the popup modal styles
|
|
this.logService.info("Applying popup modal styles", newValue.modalPosition);
|
|
applyPopupModalStyles(this.win, newValue.showTrafficButtons, newValue.modalPosition);
|
|
this.win.show();
|
|
}
|
|
}),
|
|
)
|
|
.subscribe();
|
|
|
|
this.desktopSettingsService.preventScreenshots$.subscribe((prevent) => {
|
|
if (this.win == null) {
|
|
return;
|
|
}
|
|
this.win.setContentProtection(prevent);
|
|
});
|
|
|
|
return new Promise<void>((resolve, reject) => {
|
|
try {
|
|
if (!isMacAppStore()) {
|
|
const gotTheLock = app.requestSingleInstanceLock();
|
|
if (!gotTheLock) {
|
|
app.quit();
|
|
return;
|
|
} else {
|
|
app.on("second-instance", (event, argv, workingDirectory) => {
|
|
// Someone tried to run a second instance, we should focus our window.
|
|
if (this.win != null) {
|
|
if (this.win.isMinimized() || !this.win.isVisible()) {
|
|
this.win.show();
|
|
}
|
|
this.win.focus();
|
|
}
|
|
if (isWindows() || isLinux()) {
|
|
if (this.argvCallback != null) {
|
|
this.argvCallback(argv);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// This method will be called when Electron is shutting
|
|
// down the application.
|
|
app.on("before-quit", async () => {
|
|
// Allow biometric to auto-prompt on reload
|
|
await this.biometricStateService.resetAllPromptCancelled();
|
|
this.isQuitting = true;
|
|
});
|
|
|
|
// This method will be called when Electron has finished
|
|
// initialization and is ready to create browser windows.
|
|
// Some APIs can only be used after this event occurs.
|
|
app.on("ready", async () => {
|
|
if (!isDev()) {
|
|
// This currently breaks the file portal for snap https://github.com/flatpak/xdg-desktop-portal/issues/785
|
|
if (!isSnapStore()) {
|
|
this.logService.info(
|
|
"[Process Isolation] Isolating process from debuggers and memory dumps",
|
|
);
|
|
try {
|
|
await processisolations.isolateProcess();
|
|
} catch (e) {
|
|
this.logService.error("[Process Isolation] Failed to isolate main process", e);
|
|
}
|
|
}
|
|
|
|
if (isLinux()) {
|
|
if (await processisolations.isCoreDumpingDisabled()) {
|
|
this.logService.info("Coredumps are disabled in renderer process");
|
|
} else {
|
|
this.enableRendererProcessForceCrashReload = false;
|
|
this.logService.info("Disabling coredumps in main process");
|
|
try {
|
|
await processisolations.disableCoredumps();
|
|
this.enableRendererProcessForceCrashReload = true;
|
|
} catch (e) {
|
|
this.logService.error("Failed to disable coredumps", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await this.createWindow();
|
|
resolve();
|
|
|
|
if (this.argvCallback != null) {
|
|
this.argvCallback(process.argv);
|
|
}
|
|
});
|
|
|
|
// Quit when all windows are closed.
|
|
app.on("window-all-closed", () => {
|
|
// On OS X it is common for applications and their menu bar
|
|
// to stay active until the user quits explicitly with Cmd + Q
|
|
if (!isMac() || this.isQuitting || isMacAppStore()) {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
app.on("activate", async () => {
|
|
// On OS X it's common to re-create a window in the app when the
|
|
// dock icon is clicked and there are no other windows open.
|
|
if (this.win == null) {
|
|
await this.createWindow();
|
|
} else {
|
|
// Show the window when clicking on Dock icon
|
|
this.win.show();
|
|
}
|
|
});
|
|
} catch (e) {
|
|
// Catch Error
|
|
// throw e;
|
|
reject(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Show the window with main window styles
|
|
show() {
|
|
if (this.win != null) {
|
|
applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]);
|
|
this.win.show();
|
|
}
|
|
}
|
|
|
|
// TODO: REMOVE ONCE WE CAN STOP USING FAKE POP UP BTN FROM TRAY
|
|
// Only used for development
|
|
async loadUrl(targetPath: string, modal: boolean = false) {
|
|
if (this.win == null || this.win.isDestroyed()) {
|
|
await this.createWindow("modal-app");
|
|
return;
|
|
}
|
|
|
|
await this.desktopSettingsService.setModalMode(modal);
|
|
await this.win.loadURL(
|
|
url.format({
|
|
protocol: "file:",
|
|
//pathname: `${__dirname}/index.html`,
|
|
pathname: path.join(__dirname, "/index.html"),
|
|
slashes: true,
|
|
hash: targetPath,
|
|
query: {
|
|
redirectUrl: targetPath,
|
|
},
|
|
}),
|
|
{
|
|
userAgent: cleanUserAgent(this.win.webContents.userAgent),
|
|
},
|
|
);
|
|
this.win.once("ready-to-show", () => {
|
|
this.win.show();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates the main window. The template argument is used to determine the styling of the window and what url will be loaded.
|
|
* When the template is "modal-app", the window will be styled as a modal and the passkeys page will be loaded.
|
|
* TODO: We might want to refactor the template argument to accomodate more target pages, e.g. ssh-agent.
|
|
*/
|
|
async createWindow(template: "full-app" | "modal-app" = "full-app"): Promise<void> {
|
|
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 });
|
|
|
|
// Create the browser window.
|
|
this.win = new BrowserWindow({
|
|
width: this.windowStates[mainWindowSizeKey].width,
|
|
height: this.windowStates[mainWindowSizeKey].height,
|
|
minWidth: 600,
|
|
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(),
|
|
},
|
|
});
|
|
|
|
if (template === "modal-app") {
|
|
applyPopupModalStyles(this.win);
|
|
} else {
|
|
applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]);
|
|
}
|
|
|
|
this.win.webContents.on("dom-ready", () => {
|
|
this.win.webContents.zoomFactor = this.windowStates[mainWindowSizeKey].zoomFactor ?? 1.0;
|
|
});
|
|
|
|
// Persist zoom changes from mouse wheel and programmatic zoom operations
|
|
// NOTE: This event does NOT fire for keyboard shortcuts (Ctrl+/-/0, Cmd+/-/0)
|
|
// which are handled by custom menu click handlers in ViewMenu
|
|
// We can't depend on higher level web events (like close) to do this
|
|
// because locking the vault resets window state.
|
|
this.win.webContents.on("zoom-changed", async () => {
|
|
const newZoom = this.win.webContents.zoomFactor;
|
|
this.windowStates[mainWindowSizeKey].zoomFactor = newZoom;
|
|
await this.desktopSettingsService.setWindow(this.windowStates[mainWindowSizeKey]);
|
|
});
|
|
|
|
if (this.windowStates[mainWindowSizeKey].isMaximized) {
|
|
this.win.maximize();
|
|
}
|
|
|
|
this.win.show();
|
|
|
|
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.
|
|
void this.win.loadURL(
|
|
url.format({
|
|
protocol: "file:",
|
|
pathname: path.join(__dirname, "/index.html"),
|
|
slashes: true,
|
|
}),
|
|
{
|
|
userAgent: cleanUserAgent(this.win.webContents.userAgent),
|
|
},
|
|
);
|
|
} else {
|
|
// we're in modal mode - load the passkeys page
|
|
await this.win.loadURL(
|
|
url.format({
|
|
protocol: "file:",
|
|
pathname: path.join(__dirname, "/index.html"),
|
|
slashes: true,
|
|
hash: "/passkeys",
|
|
query: {
|
|
redirectUrl: "/passkeys",
|
|
},
|
|
}),
|
|
{
|
|
userAgent: cleanUserAgent(this.win.webContents.userAgent),
|
|
},
|
|
);
|
|
}
|
|
|
|
// Open the DevTools.
|
|
if (isDev()) {
|
|
this.win.webContents.openDevTools();
|
|
}
|
|
|
|
// Emitted when the window is closed.
|
|
this.win.on("closed", async () => {
|
|
this.isClosing = false;
|
|
await this.updateWindowState(mainWindowSizeKey, this.win);
|
|
|
|
// Dereference the window object, usually you would store window
|
|
// in an array if your app supports multi windows, this is the time
|
|
// when you should delete the corresponding element.
|
|
this.win = null;
|
|
});
|
|
|
|
this.win.on("close", async () => {
|
|
this.isClosing = true;
|
|
await this.updateWindowState(mainWindowSizeKey, this.win);
|
|
});
|
|
|
|
this.win.on("maximize", async () => {
|
|
await this.updateWindowState(mainWindowSizeKey, this.win);
|
|
});
|
|
|
|
this.win.on("unmaximize", async () => {
|
|
await this.updateWindowState(mainWindowSizeKey, this.win);
|
|
});
|
|
|
|
this.win.on("resize", () => {
|
|
this.windowStateChangeHandler(mainWindowSizeKey, this.win);
|
|
});
|
|
|
|
this.win.on("move", () => {
|
|
this.windowStateChangeHandler(mainWindowSizeKey, this.win);
|
|
});
|
|
this.win.on("focus", () => {
|
|
this.win.webContents.send("messagingService", {
|
|
command: "windowIsFocused",
|
|
windowIsFocused: true,
|
|
});
|
|
});
|
|
|
|
firstValueFrom(this.desktopSettingsService.preventScreenshots$)
|
|
.then((preventScreenshots) => {
|
|
this.win.setContentProtection(preventScreenshots);
|
|
})
|
|
.catch((e) => {
|
|
this.logService.error(e);
|
|
});
|
|
|
|
if (this.createWindowCallback) {
|
|
this.createWindowCallback(this.win);
|
|
}
|
|
}
|
|
|
|
// Retrieve the background color
|
|
// Resolves background color mismatch when starting the application.
|
|
async getBackgroundColor(): Promise<string> {
|
|
let theme = await this.storageService.get("global_theming_selection");
|
|
|
|
if (
|
|
theme == null ||
|
|
!Object.values(ThemeTypes).includes(theme as Theme) ||
|
|
theme === "system"
|
|
) {
|
|
theme = nativeTheme.shouldUseDarkColors ? "dark" : "light";
|
|
}
|
|
|
|
switch (theme) {
|
|
case "light":
|
|
return "#ededed";
|
|
case "dark":
|
|
return "#15181e";
|
|
}
|
|
}
|
|
|
|
async toggleAlwaysOnTop() {
|
|
this.enableAlwaysOnTop = !this.win.isAlwaysOnTop();
|
|
this.win.setAlwaysOnTop(this.enableAlwaysOnTop);
|
|
await this.desktopSettingsService.setAlwaysOnTop(this.enableAlwaysOnTop);
|
|
}
|
|
|
|
async saveZoomFactor(zoomFactor: number) {
|
|
this.windowStates[mainWindowSizeKey].zoomFactor = zoomFactor;
|
|
await this.desktopSettingsService.setWindow(this.windowStates[mainWindowSizeKey]);
|
|
}
|
|
|
|
private windowStateChangeHandler(configKey: string, win: BrowserWindow) {
|
|
global.clearTimeout(this.windowStateChangeTimer);
|
|
this.windowStateChangeTimer = global.setTimeout(async () => {
|
|
await this.updateWindowState(configKey, win);
|
|
}, WindowEventHandlingDelay);
|
|
}
|
|
|
|
private async updateWindowState(configKey: string, win: BrowserWindow) {
|
|
if (win == null || win.isDestroyed()) {
|
|
return;
|
|
}
|
|
|
|
const modalMode = await firstValueFrom(this.desktopSettingsService.modalMode$);
|
|
|
|
if (modalMode.isModalModeActive) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const bounds = win.getBounds();
|
|
|
|
if (this.windowStates[configKey] == null) {
|
|
this.windowStates[configKey] = await firstValueFrom(this.desktopSettingsService.window$);
|
|
if (this.windowStates[configKey] == null) {
|
|
this.windowStates[configKey] = <WindowState>{};
|
|
}
|
|
}
|
|
|
|
// We treat fullscreen as maximized (would be even better to store isFullscreen as its own flag).
|
|
this.windowStates[configKey].isMaximized = win.isMaximized() || win.isFullScreen();
|
|
this.windowStates[configKey].displayBounds = screen.getDisplayMatching(bounds).bounds;
|
|
|
|
// Maybe store these as well?
|
|
// win.isFocused();
|
|
// win.isVisible();
|
|
|
|
if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) {
|
|
this.windowStates[configKey].x = bounds.x;
|
|
this.windowStates[configKey].y = bounds.y;
|
|
this.windowStates[configKey].width = bounds.width;
|
|
this.windowStates[configKey].height = bounds.height;
|
|
}
|
|
|
|
if (this.isClosing) {
|
|
this.windowStates[configKey].zoomFactor = win.webContents.zoomFactor;
|
|
}
|
|
|
|
await this.desktopSettingsService.setWindow(this.windowStates[configKey]);
|
|
} catch (e) {
|
|
this.logService.error(e);
|
|
}
|
|
}
|
|
|
|
private async getWindowState(defaultWidth: number, defaultHeight: number) {
|
|
const state = await firstValueFrom(this.desktopSettingsService.window$);
|
|
|
|
const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized);
|
|
let displayBounds: Electron.Rectangle = null;
|
|
if (!isValid) {
|
|
state.width = defaultWidth;
|
|
state.height = defaultHeight;
|
|
|
|
displayBounds = screen.getPrimaryDisplay().bounds;
|
|
} else if (this.stateHasBounds(state) && state.displayBounds) {
|
|
// Check if the display where the window was last open is still available
|
|
displayBounds = screen.getDisplayMatching(state.displayBounds).bounds;
|
|
|
|
if (
|
|
displayBounds.width !== state.displayBounds.width ||
|
|
displayBounds.height !== state.displayBounds.height ||
|
|
displayBounds.x !== state.displayBounds.x ||
|
|
displayBounds.y !== state.displayBounds.y
|
|
) {
|
|
displayBounds = screen.getPrimaryDisplay().bounds;
|
|
state.x = displayBounds.x + displayBounds.width / 2 - state.width / 2;
|
|
state.y = displayBounds.y + displayBounds.height / 2 - state.height / 2;
|
|
}
|
|
}
|
|
|
|
if (displayBounds != null) {
|
|
if (state.width > displayBounds.width && state.height > displayBounds.height) {
|
|
state.isMaximized = true;
|
|
}
|
|
|
|
if (state.width > displayBounds.width) {
|
|
state.width = displayBounds.width - 10;
|
|
}
|
|
if (state.height > displayBounds.height) {
|
|
state.height = displayBounds.height - 10;
|
|
}
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
private stateHasBounds(state: any): boolean {
|
|
return (
|
|
state != null &&
|
|
Number.isInteger(state.x) &&
|
|
Number.isInteger(state.y) &&
|
|
Number.isInteger(state.width) &&
|
|
state.width > 0 &&
|
|
Number.isInteger(state.height) &&
|
|
state.height > 0
|
|
);
|
|
}
|
|
}
|