mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
chromium importer working in sandbox
This commit is contained in:
@@ -28,7 +28,11 @@ let crossPlatform = process.argv.length > 2 && process.argv[2] === "cross-platfo
|
|||||||
function buildNapiModule(target, release = true) {
|
function buildNapiModule(target, release = true) {
|
||||||
const targetArg = target ? `--target ${target}` : "";
|
const targetArg = target ? `--target ${target}` : "";
|
||||||
const releaseArg = release ? "--release" : "";
|
const releaseArg = release ? "--release" : "";
|
||||||
child_process.execSync(`npm run build -- ${releaseArg} ${targetArg}`, { stdio: 'inherit', cwd: path.join(__dirname, "napi") });
|
child_process.execSync(`npm run build -- ${releaseArg} ${targetArg}`, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
cwd: path.join(__dirname, "napi"),
|
||||||
|
env: process.env // Pass environment variables including SANDBOX_BUILD
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildProxyBin(target, release = true) {
|
function buildProxyBin(target, release = true) {
|
||||||
|
|||||||
@@ -63,11 +63,9 @@ impl InstalledBrowserRetriever for DefaultInstalledBrowserRetriever {
|
|||||||
for (browser, config) in SUPPORTED_BROWSER_MAP.iter() {
|
for (browser, config) in SUPPORTED_BROWSER_MAP.iter() {
|
||||||
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
||||||
{
|
{
|
||||||
// macOS sandbox mode: check if we have stored security-scoped bookmark
|
// macOS sandbox mode: show all browsers, user will grant access when selected
|
||||||
if platform::ScopedBrowserAccess::has_stored_access(browser) {
|
|
||||||
browsers.push((*browser).to_string());
|
browsers.push((*browser).to_string());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(all(target_os = "macos", feature = "sandbox")))]
|
#[cfg(not(all(target_os = "macos", feature = "sandbox")))]
|
||||||
{
|
{
|
||||||
@@ -89,9 +87,13 @@ pub fn get_available_profiles(browser_name: &String) -> Result<Vec<ProfileInfo>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Request access to browser directory (sandbox mode only)
|
/// Request access to browser directory (sandbox mode only)
|
||||||
|
/// This shows the permission dialog and creates a security-scoped bookmark,
|
||||||
|
/// but does NOT start accessing the resource (that happens in resume()).
|
||||||
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
||||||
pub fn request_browser_access(browser_name: &String) -> Result<()> {
|
pub fn request_browser_access(browser_name: &String) -> Result<()> {
|
||||||
let _access = platform::ScopedBrowserAccess::request_and_start(browser_name)?;
|
eprintln!("[SANDBOX] request_browser_access called for: {}", browser_name);
|
||||||
|
platform::ScopedBrowserAccess::request_only(browser_name)?;
|
||||||
|
eprintln!("[SANDBOX] request_browser_access completed successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,27 +24,21 @@ pub struct ScopedBrowserAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ScopedBrowserAccess {
|
impl ScopedBrowserAccess {
|
||||||
/// Request permission from user and start accessing browser directory
|
pub fn request_only(browser_name: &str) -> Result<()> {
|
||||||
pub fn request_and_start(browser_name: &str) -> Result<Self> {
|
|
||||||
let c_name = CString::new(browser_name)?;
|
let c_name = CString::new(browser_name)?;
|
||||||
|
|
||||||
// Request permission (shows dialog)
|
|
||||||
let bookmark_ptr = unsafe { requestBrowserAccess(c_name.as_ptr()) };
|
let bookmark_ptr = unsafe { requestBrowserAccess(c_name.as_ptr()) };
|
||||||
if bookmark_ptr.is_null() {
|
if bookmark_ptr.is_null() {
|
||||||
return Err(anyhow!("User declined access or browser not found"));
|
return Err(anyhow!("User declined access or browser not found"));
|
||||||
}
|
}
|
||||||
unsafe { libc::free(bookmark_ptr as *mut libc::c_void) };
|
unsafe { libc::free(bookmark_ptr as *mut libc::c_void) };
|
||||||
|
|
||||||
// Start accessing
|
Ok(())
|
||||||
let path_ptr = unsafe { startBrowserAccess(c_name.as_ptr()) };
|
|
||||||
if path_ptr.is_null() {
|
|
||||||
return Err(anyhow!("Failed to start accessing browser directory"));
|
|
||||||
}
|
}
|
||||||
unsafe { libc::free(path_ptr as *mut libc::c_void) };
|
|
||||||
|
|
||||||
Ok(Self {
|
pub fn request_and_start(browser_name: &str) -> Result<Self> {
|
||||||
browser_name: browser_name.to_string(),
|
Self::request_only(browser_name)?;
|
||||||
})
|
Self::resume(browser_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resume access using previously stored bookmark
|
/// Resume access using previously stored bookmark
|
||||||
@@ -66,7 +60,6 @@ impl ScopedBrowserAccess {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if we have stored access (doesn't verify validity)
|
|
||||||
pub fn has_stored_access(browser_name: &str) -> bool {
|
pub fn has_stored_access(browser_name: &str) -> bool {
|
||||||
let Ok(c_name) = CString::new(browser_name) else {
|
let Ok(c_name) = CString::new(browser_name) else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
1
apps/desktop/desktop_native/napi/index.d.ts
vendored
1
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -249,6 +249,7 @@ export declare namespace chromium_importer {
|
|||||||
export function getMetadata(): Record<string, NativeImporterMetadata>
|
export function getMetadata(): Record<string, NativeImporterMetadata>
|
||||||
export function getAvailableProfiles(browser: string): Array<ProfileInfo>
|
export function getAvailableProfiles(browser: string): Array<ProfileInfo>
|
||||||
export function importLogins(browser: string, profileId: string): Promise<Array<LoginImportResult>>
|
export function importLogins(browser: string, profileId: string): Promise<Array<LoginImportResult>>
|
||||||
|
export function requestBrowserAccess(browser: string): void
|
||||||
}
|
}
|
||||||
export declare namespace autotype {
|
export declare namespace autotype {
|
||||||
export function getForegroundWindowTitle(): string
|
export function getForegroundWindowTitle(): string
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ if (isRelease) {
|
|||||||
process.env.RUST_LOG = 'debug';
|
process.env.RUST_LOG = 'debug';
|
||||||
}
|
}
|
||||||
|
|
||||||
execSync(`napi build --platform --js false`, { stdio: 'inherit', env: process.env });
|
const featuresArg = process.env.SANDBOX_BUILD === '1' ? '--features sandbox' : '';
|
||||||
|
if (featuresArg) {
|
||||||
|
console.log('Building with sandbox feature enabled.');
|
||||||
|
}
|
||||||
|
|
||||||
|
execSync(`napi build --platform --js false ${featuresArg}`, { stdio: 'inherit', env: process.env });
|
||||||
|
|
||||||
|
|
||||||
/* Mac App Store build with sandboxing - Does this belong here?
|
/* Mac App Store build with sandboxing - Does this belong here?
|
||||||
|
|||||||
@@ -1182,12 +1182,19 @@ pub mod chromium_importer {
|
|||||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(all(target_os = "macos", feature = "sandbox"), napi)]
|
#[napi]
|
||||||
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
|
||||||
pub fn request_browser_access(browser: String) -> napi::Result<()> {
|
pub fn request_browser_access(browser: String) -> napi::Result<()> {
|
||||||
|
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
||||||
|
{
|
||||||
chromium_importer::chromium::request_browser_access(&browser)
|
chromium_importer::chromium::request_browser_access(&browser)
|
||||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||||
}
|
}
|
||||||
|
#[cfg(not(all(target_os = "macos", feature = "sandbox")))]
|
||||||
|
{
|
||||||
|
// No-op when built without sandbox feature
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
|
|||||||
@@ -17,16 +17,24 @@ import Foundation
|
|||||||
/// Request access to a specific browser's directory
|
/// Request access to a specific browser's directory
|
||||||
/// Returns security bookmark data (used to persist permissions) as base64 string, or nil if user declined
|
/// Returns security bookmark data (used to persist permissions) as base64 string, or nil if user declined
|
||||||
@objc public func requestAccessToBroswerDir(_ browserName: String) -> String? {
|
@objc public func requestAccessToBroswerDir(_ browserName: String) -> String? {
|
||||||
|
NSLog("[SWIFT] requestAccessToBroswerDir called for: \(browserName)")
|
||||||
|
|
||||||
guard let relativePath = browserPaths[browserName] else {
|
guard let relativePath = browserPaths[browserName] else {
|
||||||
NSLog("Unknown browser: \(browserName)")
|
NSLog("[SWIFT] Unknown browser: \(browserName)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let homeDir = FileManager.default.homeDirectoryForCurrentUser
|
let homeDir = FileManager.default.homeDirectoryForCurrentUser
|
||||||
let browserPath = homeDir.appendingPathComponent(relativePath)
|
let browserPath = homeDir.appendingPathComponent(relativePath)
|
||||||
|
|
||||||
// Open file picker at home directory and provide instructions to grant access to browserPath
|
NSLog("[SWIFT] Browser path: \(browserPath.path)")
|
||||||
// Mac OS will automatically request permission to access this location from the sandbox when the location is selected here
|
|
||||||
|
// NSOpenPanel must be run on the main thread
|
||||||
|
var selectedURL: URL?
|
||||||
|
var panelResult: NSApplication.ModalResponse = .cancel
|
||||||
|
|
||||||
|
if Thread.isMainThread {
|
||||||
|
NSLog("[SWIFT] Already on main thread")
|
||||||
let openPanel = NSOpenPanel()
|
let openPanel = NSOpenPanel()
|
||||||
openPanel.message =
|
openPanel.message =
|
||||||
"Please select your \(browserName) data folder\n\nExpected location:\n\(browserPath.path)"
|
"Please select your \(browserName) data folder\n\nExpected location:\n\(browserPath.path)"
|
||||||
@@ -34,16 +42,42 @@ import Foundation
|
|||||||
openPanel.allowsMultipleSelection = false
|
openPanel.allowsMultipleSelection = false
|
||||||
openPanel.canChooseDirectories = true
|
openPanel.canChooseDirectories = true
|
||||||
openPanel.canChooseFiles = false
|
openPanel.canChooseFiles = false
|
||||||
openPanel.directoryURL = browserPath.deletingLastPathComponent() // home directory
|
openPanel.directoryURL = browserPath.deletingLastPathComponent()
|
||||||
|
|
||||||
guard openPanel.runModal() == .OK, let url = openPanel.url else {
|
NSLog("[SWIFT] About to call openPanel.runModal()")
|
||||||
NSLog("User cancelled access request")
|
panelResult = openPanel.runModal()
|
||||||
|
selectedURL = openPanel.url
|
||||||
|
NSLog("[SWIFT] runModal returned: \(panelResult.rawValue)")
|
||||||
|
} else {
|
||||||
|
NSLog("[SWIFT] Dispatching to main queue...")
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
NSLog("[SWIFT] Inside main queue dispatch block")
|
||||||
|
let openPanel = NSOpenPanel()
|
||||||
|
openPanel.message =
|
||||||
|
"Please select your \(browserName) data folder\n\nExpected location:\n\(browserPath.path)"
|
||||||
|
openPanel.prompt = "Grant Access"
|
||||||
|
openPanel.allowsMultipleSelection = false
|
||||||
|
openPanel.canChooseDirectories = true
|
||||||
|
openPanel.canChooseFiles = false
|
||||||
|
openPanel.directoryURL = browserPath.deletingLastPathComponent()
|
||||||
|
|
||||||
|
NSLog("[SWIFT] About to call openPanel.runModal()")
|
||||||
|
panelResult = openPanel.runModal()
|
||||||
|
selectedURL = openPanel.url
|
||||||
|
NSLog("[SWIFT] runModal returned: \(panelResult.rawValue)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard panelResult == .OK, let url = selectedURL else {
|
||||||
|
NSLog("[SWIFT] User cancelled access request or panel failed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NSLog("[SWIFT] User selected URL: \(url.path)")
|
||||||
|
|
||||||
let localStatePath = url.appendingPathComponent("Local State")
|
let localStatePath = url.appendingPathComponent("Local State")
|
||||||
guard FileManager.default.fileExists(atPath: localStatePath.path) else {
|
guard FileManager.default.fileExists(atPath: localStatePath.path) else {
|
||||||
NSLog("Selected folder doesn't appear to be a valid \(browserName) directory")
|
NSLog("[SWIFT] Selected folder doesn't appear to be a valid \(browserName) directory")
|
||||||
|
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.messageText = "Invalid Folder"
|
alert.messageText = "Invalid Folder"
|
||||||
@@ -64,9 +98,10 @@ import Foundation
|
|||||||
)
|
)
|
||||||
|
|
||||||
saveBookmark(bookmarkData, forBrowser: browserName)
|
saveBookmark(bookmarkData, forBrowser: browserName)
|
||||||
|
NSLog("[SWIFT] Successfully created and saved bookmark")
|
||||||
return bookmarkData.base64EncodedString()
|
return bookmarkData.base64EncodedString()
|
||||||
} catch {
|
} catch {
|
||||||
NSLog("Failed to create bookmark: \(error)")
|
NSLog("[SWIFT] Failed to create bookmark: \(error)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,14 +113,6 @@ import Foundation
|
|||||||
|
|
||||||
/// Start accessing a browser directory using stored bookmark
|
/// Start accessing a browser directory using stored bookmark
|
||||||
/// Returns the resolved path, or nil if bookmark is invalid/revoked
|
/// Returns the resolved path, or nil if bookmark is invalid/revoked
|
||||||
/*
|
|
||||||
This could return nil if:
|
|
||||||
The user doesn’t have access to this URL
|
|
||||||
The URL isn’t a security scoped URL
|
|
||||||
T The directory doesn’t need it (~/Downloads)
|
|
||||||
|
|
||||||
https://benscheirman.com/2019/10/troubleshooting-appkit-file-permissions.html
|
|
||||||
*/
|
|
||||||
@objc public func startAccessingBrowser(_ browserName: String) -> String? {
|
@objc public func startAccessingBrowser(_ browserName: String) -> String? {
|
||||||
guard let bookmarkData = loadBookmark(forBrowser: browserName) else {
|
guard let bookmarkData = loadBookmark(forBrowser: browserName) else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
"build:renderer:watch": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name renderer --watch",
|
"build:renderer:watch": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name renderer --watch",
|
||||||
"electron": "node ./scripts/start.js",
|
"electron": "node ./scripts/start.js",
|
||||||
"electron:ignore": "node ./scripts/start.js --ignore-certificate-errors",
|
"electron:ignore": "node ./scripts/start.js --ignore-certificate-errors",
|
||||||
|
"electron:sandbox": "SANDBOX_BUILD=1 node ./scripts/start.js",
|
||||||
"flatpak:dev": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --force-clean --install --user ../../.flatpak/ ./resources/com.bitwarden.desktop.devel.yaml && flatpak run com.bitwarden.desktop",
|
"flatpak:dev": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --force-clean --install --user ../../.flatpak/ ./resources/com.bitwarden.desktop.devel.yaml && flatpak run com.bitwarden.desktop",
|
||||||
"clean:dist": "rimraf ./dist",
|
"clean:dist": "rimraf ./dist",
|
||||||
"pack:dir": "npm run clean:dist && electron-builder --dir -p never",
|
"pack:dir": "npm run clean:dist && electron-builder --dir -p never",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ concurrently(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Elec",
|
name: "Elec",
|
||||||
command: `npx wait-on ./build/main.js && npx electron --no-sandbox --inspect=5858 ${args.join(
|
command: `npx wait-on ./build/main.js && npx electron ${process.env.SANDBOX_BUILD ? "" : "--no-sandbox "}--inspect=5858 ${args.join(
|
||||||
" ",
|
" ",
|
||||||
)} ./build --watch`,
|
)} ./build --watch`,
|
||||||
prefixColor: "green",
|
prefixColor: "green",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
|
|
||||||
import { chromium_importer } from "@bitwarden/desktop-napi";
|
import { chromium_importer } from "@bitwarden/desktop-napi";
|
||||||
@@ -8,6 +9,23 @@ export class ChromiumImporterService {
|
|||||||
return await chromium_importer.getMetadata();
|
return await chromium_importer.getMetadata();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("chromium_importer.requestBrowserAccess", async (event, browser: string) => {
|
||||||
|
console.log("[IPC] requestBrowserAccess handler called for:", browser);
|
||||||
|
console.log("[IPC] chromium_importer keys:", Object.keys(chromium_importer));
|
||||||
|
console.log(
|
||||||
|
"[IPC] requestBrowserAccess exists?",
|
||||||
|
typeof chromium_importer.requestBrowserAccess,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (chromium_importer.requestBrowserAccess) {
|
||||||
|
console.log("[IPC] Calling native requestBrowserAccess");
|
||||||
|
return await chromium_importer.requestBrowserAccess(browser);
|
||||||
|
}
|
||||||
|
// No-op if not compiled with sandbox support
|
||||||
|
console.log("[IPC] requestBrowserAccess not found, returning no-op");
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle("chromium_importer.getAvailableProfiles", async (event, browser: string) => {
|
ipcMain.handle("chromium_importer.getAvailableProfiles", async (event, browser: string) => {
|
||||||
return await chromium_importer.getAvailableProfiles(browser);
|
return await chromium_importer.getAvailableProfiles(browser);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
@@ -39,6 +40,10 @@ export class ImportDesktopComponent {
|
|||||||
protected disabled = false;
|
protected disabled = false;
|
||||||
protected loading = false;
|
protected loading = false;
|
||||||
|
|
||||||
|
// Bind callbacks in constructor to maintain reference equality
|
||||||
|
protected readonly onLoadProfilesFromBrowser = this._onLoadProfilesFromBrowser.bind(this);
|
||||||
|
protected readonly onImportFromBrowser = this._onImportFromBrowser.bind(this);
|
||||||
|
|
||||||
constructor(public dialogRef: DialogRef) {}
|
constructor(public dialogRef: DialogRef) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,11 +53,24 @@ export class ImportDesktopComponent {
|
|||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onLoadProfilesFromBrowser(browser: string): Promise<chromium_importer.ProfileInfo[]> {
|
private async _onLoadProfilesFromBrowser(
|
||||||
|
browser: string,
|
||||||
|
): Promise<chromium_importer.ProfileInfo[]> {
|
||||||
|
console.log("[SANDBOX] onLoadProfilesFromBrowser called for:", browser);
|
||||||
|
// Request browser access (required for sandboxed builds, no-op otherwise)
|
||||||
|
try {
|
||||||
|
console.log("[SANDBOX] Calling requestBrowserAccess...");
|
||||||
|
await ipc.tools.chromiumImporter.requestBrowserAccess(browser);
|
||||||
|
console.log("[SANDBOX] requestBrowserAccess completed successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[SANDBOX] requestBrowserAccess failed:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
console.log("[SANDBOX] Calling getAvailableProfiles...");
|
||||||
return ipc.tools.chromiumImporter.getAvailableProfiles(browser);
|
return ipc.tools.chromiumImporter.getAvailableProfiles(browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onImportFromBrowser(
|
private _onImportFromBrowser(
|
||||||
browser: string,
|
browser: string,
|
||||||
profile: string,
|
profile: string,
|
||||||
): Promise<chromium_importer.LoginImportResult[]> {
|
): Promise<chromium_importer.LoginImportResult[]> {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import type { chromium_importer } from "@bitwarden/desktop-napi";
|
|||||||
const chromiumImporter = {
|
const chromiumImporter = {
|
||||||
getMetadata: (): Promise<Record<string, chromium_importer.NativeImporterMetadata>> =>
|
getMetadata: (): Promise<Record<string, chromium_importer.NativeImporterMetadata>> =>
|
||||||
ipcRenderer.invoke("chromium_importer.getMetadata"),
|
ipcRenderer.invoke("chromium_importer.getMetadata"),
|
||||||
|
// Request browser access for sandboxed builds (no-op in non-sandboxed builds)
|
||||||
|
requestBrowserAccess: (browser: string): Promise<void> =>
|
||||||
|
ipcRenderer.invoke("chromium_importer.requestBrowserAccess", browser),
|
||||||
getAvailableProfiles: (browser: string): Promise<chromium_importer.ProfileInfo[]> =>
|
getAvailableProfiles: (browser: string): Promise<chromium_importer.ProfileInfo[]> =>
|
||||||
ipcRenderer.invoke("chromium_importer.getAvailableProfiles", browser),
|
ipcRenderer.invoke("chromium_importer.getAvailableProfiles", browser),
|
||||||
importLogins: (
|
importLogins: (
|
||||||
|
|||||||
@@ -42,14 +42,40 @@ function positionWindow(window: BrowserWindow, position?: Position) {
|
|||||||
export function applyMainWindowStyles(window: BrowserWindow, existingWindowState: WindowState) {
|
export function applyMainWindowStyles(window: BrowserWindow, existingWindowState: WindowState) {
|
||||||
window.setMinimumSize(680, 500);
|
window.setMinimumSize(680, 500);
|
||||||
|
|
||||||
// need to guard against null/undefined values
|
// need to guard against null/undefined values and ensure values are valid
|
||||||
|
if (existingWindowState) {
|
||||||
if (existingWindowState?.width && existingWindowState?.height) {
|
if (
|
||||||
window.setSize(existingWindowState.width, existingWindowState.height);
|
typeof existingWindowState.width === "number" &&
|
||||||
|
typeof existingWindowState.height === "number" &&
|
||||||
|
Number.isFinite(existingWindowState.width) &&
|
||||||
|
Number.isFinite(existingWindowState.height) &&
|
||||||
|
existingWindowState.width > 0 &&
|
||||||
|
existingWindowState.height > 0
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// Ensure values are integers as Electron expects integer pixel values
|
||||||
|
window.setSize(
|
||||||
|
Math.round(existingWindowState.width),
|
||||||
|
Math.round(existingWindowState.height),
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// Silently fail - window will use default size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingWindowState?.x && existingWindowState?.y) {
|
if (
|
||||||
window.setPosition(existingWindowState.x, existingWindowState.y);
|
typeof existingWindowState.x === "number" &&
|
||||||
|
typeof existingWindowState.y === "number" &&
|
||||||
|
Number.isFinite(existingWindowState.x) &&
|
||||||
|
Number.isFinite(existingWindowState.y)
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// Ensure values are integers as Electron expects integer pixel values
|
||||||
|
window.setPosition(Math.round(existingWindowState.x), Math.round(existingWindowState.y));
|
||||||
|
} catch {
|
||||||
|
// Silently fail - window will use default position
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.setWindowButtonVisibility?.(true);
|
window.setWindowButtonVisibility?.(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user