Skip to content

Commit

Permalink
[BEEEP][PM-14388] Better dev experience on desktop-browser IPC (#11822)
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia authored Nov 1, 2024
1 parent 4a2c14d commit 5eae599
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 50 deletions.
9 changes: 8 additions & 1 deletion apps/desktop/desktop_native/core/src/ipc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn path(name: &str) -> std::path::PathBuf {
format!(r"\\.\pipe\{hash_b64}.app.{name}").into()
}

#[cfg(target_os = "macos")]
#[cfg(all(target_os = "macos", not(debug_assertions)))]
{
let mut home = dirs::home_dir().unwrap();

Expand All @@ -53,6 +53,13 @@ pub fn path(name: &str) -> std::path::PathBuf {
tmp.join(format!("app.{name}"))
}

#[cfg(all(target_os = "macos", debug_assertions))]
{
// When running in debug mode, we use the tmp dir because the app is not sandboxed
let dir = std::env::temp_dir();
dir.join(format!("app.{name}"))
}

#[cfg(target_os = "linux")]
{
// On Linux, we use the user's cache directory.
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/app/accounts/settings.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
async saveBrowserIntegration() {
if (
ipc.platform.deviceType === DeviceType.MacOsDesktop &&
!this.platformUtilsService.isMacAppStore()
!this.platformUtilsService.isMacAppStore() &&
!ipc.platform.isDev
) {
await this.dialogService.openSimpleDialog({
title: { key: "browserIntegrationUnsupportedTitle" },
Expand Down
165 changes: 117 additions & 48 deletions apps/desktop/src/main/native-messaging.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,7 @@ export class NativeMessagingMain {
};
const chromeJson = {
...baseJson,
...{
allowed_origins: [
// Chrome extension
"chrome-extension://nngceckbapebfimnlniiiahkandclblb/",
// Chrome beta extension
"chrome-extension://hccnnhgbibccigepcmlgppchkpfdophk/",
// Edge extension
"chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh/",
// Opera extension
"chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn/",
],
},
allowed_origins: await this.loadChromeIds(),
};

switch (process.platform) {
Expand Down Expand Up @@ -180,35 +169,26 @@ export class NativeMessagingMain {
}
break;
}
case "linux":
if (existsSync(`${this.homedir()}/.mozilla/`)) {
await this.writeManifest(
`${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`,
firefoxJson,
);
}

if (existsSync(`${this.homedir()}/.config/google-chrome/`)) {
await this.writeManifest(
`${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`,
chromeJson,
);
}

if (existsSync(`${this.homedir()}/.config/microsoft-edge/`)) {
await this.writeManifest(
`${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`,
chromeJson,
);
}

if (existsSync(`${this.homedir()}/.config/chromium/`)) {
await this.writeManifest(
`${this.homedir()}/.config/chromium/NativeMessagingHosts/com.8bit.bitwarden.json`,
chromeJson,
);
case "linux": {
for (const [key, value] of Object.entries(this.getLinuxNMHS())) {
if (existsSync(value)) {
if (key === "Firefox") {
await this.writeManifest(
path.join(value, "native-messaging-hosts", "com.8bit.bitwarden.json"),
firefoxJson,
);
} else {
await this.writeManifest(
path.join(value, "NativeMessagingHosts", "com.8bit.bitwarden.json"),
chromeJson,
);
}
} else {
this.logService.warning(`${key} not found, skipping.`);
}
}
break;
}
default:
break;
}
Expand Down Expand Up @@ -260,15 +240,18 @@ export class NativeMessagingMain {
break;
}
case "linux": {
await this.removeIfExists(
`${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`,
);
await this.removeIfExists(
`${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`,
);
await this.removeIfExists(
`${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`,
);
for (const [key, value] of Object.entries(this.getLinuxNMHS())) {
if (key === "Firefox") {
await this.removeIfExists(
path.join(value, "native-messaging-hosts", "com.8bit.bitwarden.json"),
);
} else {
await this.removeIfExists(
path.join(value, "NativeMessagingHosts", "com.8bit.bitwarden.json"),
);
}
}

break;
}
default:
Expand Down Expand Up @@ -317,6 +300,15 @@ export class NativeMessagingMain {
/* eslint-enable no-useless-escape */
}

private getLinuxNMHS() {
return {
Firefox: `${this.homedir()}/.mozilla/`,
Chrome: `${this.homedir()}/.config/google-chrome/`,
Chromium: `${this.homedir()}/.config/chromium/`,
"Microsoft Edge": `${this.homedir()}/.config/microsoft-edge/`,
};
}

private async writeManifest(destination: string, manifest: object) {
this.logService.debug(`Writing manifest: ${destination}`);

Expand All @@ -327,6 +319,83 @@ export class NativeMessagingMain {
await fs.writeFile(destination, JSON.stringify(manifest, null, 2));
}

private async loadChromeIds(): Promise<string[]> {
const ids: Set<string> = new Set([
// Chrome extension
"chrome-extension://nngceckbapebfimnlniiiahkandclblb/",
// Chrome beta extension
"chrome-extension://hccnnhgbibccigepcmlgppchkpfdophk/",
// Edge extension
"chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh/",
// Opera extension
"chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn/",
]);

if (!isDev()) {
return Array.from(ids);
}

// The dev builds of the extension have a different random ID per user, so to make development easier
// we try to find the extension IDs from the user's Chrome profiles when we're running in dev mode.
let chromePaths: string[];
switch (process.platform) {
case "darwin": {
chromePaths = Object.entries(this.getDarwinNMHS())
.filter(([key]) => key !== "Firefox")
.map(([, value]) => value);
break;
}
case "linux": {
chromePaths = Object.entries(this.getLinuxNMHS())
.filter(([key]) => key !== "Firefox")
.map(([, value]) => value);
break;
}
case "win32": {
// TODO: Add more supported browsers for Windows?
chromePaths = [
path.join(process.env.LOCALAPPDATA, "Microsoft", "Edge", "User Data"),
path.join(process.env.LOCALAPPDATA, "Google", "Chrome", "User Data"),
];
break;
}
}

for (const chromePath of chromePaths) {
try {
// The chrome profile directories are named "Default", "Profile 1", "Profile 2", etc.
const profiles = (await fs.readdir(chromePath)).filter((f) => {
const lower = f.toLowerCase();
return lower == "default" || lower.startsWith("profile ");
});

for (const profile of profiles) {
try {
// Read the profile Preferences file and find the extension commands section
const prefs = JSON.parse(
await fs.readFile(path.join(chromePath, profile, "Preferences"), "utf8"),
);
const commands: Map<string, any> = prefs.extensions.commands;

// If one of the commands is autofill_login or generate_password, we know it's probably the Bitwarden extension
for (const { command_name, extension } of Object.values(commands)) {
if (command_name === "autofill_login" || command_name === "generate_password") {
ids.add(`chrome-extension://${extension}/`);
this.logService.info(`Found extension from ${chromePath}: ${extension}`);
}
}
} catch (e) {
this.logService.info(`Error reading preferences: ${e}`);
}
}
} catch (e) {
// Browser is not installed, we can just skip it
}
}

return Array.from(ids);
}

private binaryPath() {
const ext = process.platform === "win32" ? ".exe" : "";

Expand Down

0 comments on commit 5eae599

Please sign in to comment.