From ed4d481e4d26d3349506407ea113da2238a4ab16 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:09:17 -0400 Subject: [PATCH] [PM-7646][PM-5506] Revert IPC changes (#10946) * Revert "Remove unnecessary plist keys in desktop_proxy (#10933)" This reverts commit 4dbb036df11f004c424429f61b448758eb35c1c8. * Revert "Fix TestFlight errors caused by desktop_proxy (#10928)" This reverts commit 40cb4b5353ce64b0ded69f29f3f84ccb8386fdbc. * Revert "[PM-5506] Enable electron fuses (#10073)" This reverts commit 78c5e9c7061fff7f550ebeb28711a47143f3c0f5. * Revert "[PM-7846] Implement a rust based native messaging proxy and IPC system (#9894)" This reverts commit 55874b72bfb59acc3fd31facfac0d676ff0807b2. --- .github/workflows/build-desktop.yml | 45 ++-- apps/desktop/desktop_native/.gitignore | 1 - apps/desktop/desktop_native/Cargo.lock | 254 +----------------- apps/desktop/desktop_native/Cargo.toml | 2 +- apps/desktop/desktop_native/build.js | 68 ----- apps/desktop/desktop_native/core/Cargo.toml | 37 +-- .../desktop_native/core/src/ipc/client.rs | 102 ------- .../desktop_native/core/src/ipc/mod.rs | 64 ----- .../desktop_native/core/src/ipc/server.rs | 232 ---------------- apps/desktop/desktop_native/core/src/lib.rs | 6 - apps/desktop/desktop_native/napi/Cargo.toml | 6 +- apps/desktop/desktop_native/napi/build.js | 24 ++ apps/desktop/desktop_native/napi/index.d.ts | 30 --- apps/desktop/desktop_native/napi/index.js | 8 +- apps/desktop/desktop_native/napi/package.json | 4 +- apps/desktop/desktop_native/napi/src/lib.rs | 100 ------- apps/desktop/desktop_native/proxy/Cargo.toml | 19 -- apps/desktop/desktop_native/proxy/src/main.rs | 140 ---------- apps/desktop/electron-builder.json | 21 +- apps/desktop/package.json | 2 +- .../entitlements.desktop_proxy.plist | 12 - apps/desktop/resources/entitlements.mas.plist | 4 - .../resources/info.desktop_proxy.plist | 8 - apps/desktop/resources/native-messaging.bat | 7 + apps/desktop/scripts/after-pack.js | 138 +--------- apps/desktop/src/entry.ts | 42 ++- apps/desktop/src/main.ts | 15 +- .../desktop/src/main/native-messaging.main.ts | 97 +++---- apps/desktop/src/proxy/ipc.ts | 78 ++++++ .../src/proxy/native-messaging-proxy.ts | 23 ++ apps/desktop/src/proxy/nativemessage.ts | 95 +++++++ package-lock.json | 28 -- package.json | 1 - 33 files changed, 348 insertions(+), 1365 deletions(-) delete mode 100644 apps/desktop/desktop_native/build.js delete mode 100644 apps/desktop/desktop_native/core/src/ipc/client.rs delete mode 100644 apps/desktop/desktop_native/core/src/ipc/mod.rs delete mode 100644 apps/desktop/desktop_native/core/src/ipc/server.rs create mode 100644 apps/desktop/desktop_native/napi/build.js delete mode 100644 apps/desktop/desktop_native/proxy/Cargo.toml delete mode 100644 apps/desktop/desktop_native/proxy/src/main.rs delete mode 100644 apps/desktop/resources/entitlements.desktop_proxy.plist delete mode 100644 apps/desktop/resources/info.desktop_proxy.plist create mode 100644 apps/desktop/resources/native-messaging.bat create mode 100644 apps/desktop/src/proxy/ipc.ts create mode 100644 apps/desktop/src/proxy/native-messaging-proxy.ts create mode 100644 apps/desktop/src/proxy/nativemessage.ts diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index e82b490f81..8ac65d257c 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -174,21 +174,20 @@ jobs: with: path: | apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* ${{ env.RUNNER_TEMP }}/.cargo/registry ${{ env.RUNNER_TEMP }}/.cargo/git key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native + working-directory: apps/desktop/desktop_native/napi env: PKG_CONFIG_ALLOW_CROSS: true PKG_CONFIG_ALL_STATIC: true TARGET: musl run: | rustup target add x86_64-unknown-linux-musl - node build.js cross-platform + npm run build:cross-platform - name: Build application run: npm run dist:lin @@ -302,15 +301,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build & Sign (dev) env: @@ -587,15 +584,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build application (dev) run: npm run build @@ -753,15 +748,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -972,15 +965,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -1177,15 +1168,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' diff --git a/apps/desktop/desktop_native/.gitignore b/apps/desktop/desktop_native/.gitignore index 1cfa7dafc2..96e7a71e1b 100644 --- a/apps/desktop/desktop_native/.gitignore +++ b/apps/desktop/desktop_native/.gitignore @@ -4,4 +4,3 @@ index.node **/.DS_Store npm-debug.log* *.node -dist diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index d96e0642fa..6c73c3622b 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -482,15 +482,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - [[package]] name = "derive-new" version = "0.6.0" @@ -512,14 +503,10 @@ dependencies = [ "base64", "cbc", "core-foundation", - "dirs", - "futures", "gio", - "interprocess", "keytar", "libc", "libsecret", - "log", "rand", "retry", "scopeguard", @@ -528,7 +515,6 @@ dependencies = [ "sha2", "thiserror", "tokio", - "tokio-util", "typenum", "widestring", "windows", @@ -545,22 +531,6 @@ dependencies = [ "napi", "napi-build", "napi-derive", - "tokio", - "tokio-util", -] - -[[package]] -name = "desktop_proxy" -version = "0.0.0" -dependencies = [ - "anyhow", - "desktop_core", - "embed_plist", - "futures", - "log", - "simplelog", - "tokio", - "tokio-util", ] [[package]] @@ -573,27 +543,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - [[package]] name = "dlib" version = "0.5.2" @@ -603,24 +552,12 @@ dependencies = [ "libloading", ] -[[package]] -name = "doctest-file" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" - [[package]] name = "downcast-rs" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - [[package]] name = "endi" version = "1.1.0" @@ -709,21 +646,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.30" @@ -731,7 +653,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -799,7 +720,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -994,27 +914,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "interprocess" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13" -dependencies = [ - "doctest-file", - "futures-core", - "libc", - "recvmsg", - "tokio", - "widestring", - "windows-sys 0.52.0", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - [[package]] name = "keytar" version = "0.1.6" @@ -1052,16 +951,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "libsecret" version = "0.5.0" @@ -1149,22 +1038,11 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - [[package]] name = "napi" -version = "2.16.7" +version = "2.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633e41b2b983cf7983134f0c50986ca524d0caf38a2c6fc893ea3fa2e26abb0c" +checksum = "dfc300228808a0e6aea5a58115c82889240bcf8dab16fc25ad675b33e454b368" dependencies = [ "bitflags", "ctor", @@ -1182,9 +1060,9 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" [[package]] name = "napi-derive" -version = "2.16.6" +version = "2.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a8a778fd367b13c64232e58632514b795514ece491ce136d96e976d34a3eb8" +checksum = "e0e034ddf6155192cf83f267ede763fe6c164dfa9971585436b16173718d94c4" dependencies = [ "cfg-if", "convert_case", @@ -1253,12 +1131,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num_cpus" version = "1.16.0" @@ -1269,15 +1141,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -1392,12 +1255,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "ordered-stream" version = "0.2.0" @@ -1501,12 +1358,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1582,12 +1433,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "recvmsg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" - [[package]] name = "redox_syscall" version = "0.5.3" @@ -1597,17 +1442,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" version = "1.10.6" @@ -1789,17 +1623,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simplelog" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" -dependencies = [ - "log", - "termcolor", - "time", -] - [[package]] name = "slab" version = "0.4.9" @@ -1815,16 +1638,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -1903,39 +1716,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tokio" version = "1.38.0" @@ -1944,13 +1724,9 @@ checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", - "libc", - "mio", "num_cpus", "pin-project-lite", - "socket2", "tokio-macros", - "windows-sys 0.48.0", ] [[package]] @@ -1964,19 +1740,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" version = "0.8.19" @@ -2272,15 +2035,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 6525b38162..c6b77473b2 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["napi", "core", "proxy"] +members = ["napi", "core"] diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js deleted file mode 100644 index f2f012bf08..0000000000 --- a/apps/desktop/desktop_native/build.js +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const child_process = require("child_process"); -const fs = require("fs"); -const path = require("path"); -const process = require("process"); - -let crossPlatform = process.argv.length > 2 && process.argv[2] === "cross-platform"; - -function buildNapiModule(target, release = true) { - const targetArg = target ? `--target ${target}` : ""; - const releaseArg = release ? "--release" : ""; - return child_process.execSync(`npm run build -- ${releaseArg} ${targetArg}`, { stdio: 'inherit', cwd: path.join(__dirname, "napi") }); -} - -function buildProxyBin(target, release = true) { - const targetArg = target ? `--target ${target}` : ""; - const releaseArg = release ? "--release" : ""; - return child_process.execSync(`cargo build --bin desktop_proxy ${releaseArg} ${targetArg}`, {stdio: 'inherit', cwd: path.join(__dirname, "proxy")}); -} - -if (!crossPlatform) { - console.log("Building native modules in debug mode for the native architecture"); - buildNapiModule(false, false); - buildProxyBin(false, false); - return; -} - -// Note that targets contains pairs of [rust target, node arch] -// We do this to move the output binaries to a location that can -// be easily accessed from electron-builder using ${os} and ${arch} -let targets = []; -switch (process.platform) { - case "win32": - targets = [ - ["i686-pc-windows-msvc", 'ia32'], - ["x86_64-pc-windows-msvc", 'x64'], - ["aarch64-pc-windows-msvc", 'arm64'] - ]; - break; - - case "darwin": - targets = [ - ["x86_64-apple-darwin", 'x64'], - ["aarch64-apple-darwin", 'arm64'] - ]; - break; - - default: - targets = [ - ['x86_64-unknown-linux-musl', 'x64'] - ]; - - process.env["PKG_CONFIG_ALLOW_CROSS"] = "1"; - process.env["PKG_CONFIG_ALL_STATIC"] = "1"; - break; -} - -console.log("Cross building native modules for the targets: ", targets.map(([target, _]) => target).join(", ")); - -fs.mkdirSync(path.join(__dirname, "dist"), { recursive: true }); - -targets.forEach(([target, nodeArch]) => { - buildNapiModule(target); - buildProxyBin(target); - - const ext = process.platform === "win32" ? ".exe" : ""; - fs.copyFileSync(path.join(__dirname, "target", target, "release", `desktop_proxy${ext}`), path.join(__dirname, "dist", `desktop_proxy.${process.platform}-${nodeArch}${ext}`)); -}); diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 46b22a5b74..d885920883 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -6,21 +6,9 @@ version = "0.0.0" publish = false [features] -default = ["sys"] +default = [] manual_test = [] -sys = [ - "dep:widestring", - "dep:windows", - "dep:core-foundation", - "dep:security-framework", - "dep:security-framework-sys", - "dep:gio", - "dep:libsecret", - "dep:zbus", - "dep:zbus_polkit", -] - [dependencies] aes = "=0.8.4" anyhow = "=1.0.86" @@ -29,22 +17,17 @@ arboard = { version = "=3.4.0", default-features = false, features = [ ] } base64 = "=0.22.1" cbc = { version = "=0.1.2", features = ["alloc"] } -dirs = "=5.0.1" -futures = "=0.3.30" -interprocess = { version = "=2.2.1", features = ["tokio"] } libc = "=0.2.155" -log = "=0.4.22" rand = "=0.8.5" retry = "=2.0.0" scopeguard = "=1.2.0" sha2 = "=0.10.8" thiserror = "=1.0.61" tokio = { version = "=1.38.0", features = ["io-util", "sync", "macros"] } -tokio-util = "=0.7.11" typenum = "=1.17.0" [target.'cfg(windows)'.dependencies] -widestring = { version = "=1.1.0", optional = true } +widestring = "=1.1.0" windows = { version = "=0.57.0", features = [ "Foundation", "Security_Credentials_UI", @@ -55,18 +38,18 @@ windows = { version = "=0.57.0", features = [ "Win32_System_WinRT", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging", -], optional = true } +] } [target.'cfg(windows)'.dev-dependencies] keytar = "=0.1.6" [target.'cfg(target_os = "macos")'.dependencies] -core-foundation = { version = "=0.9.4", optional = true } -security-framework = { version = "=2.11.0", optional = true } -security-framework-sys = { version = "=2.11.0", optional = true } +core-foundation = "=0.9.4" +security-framework = "=2.11.0" +security-framework-sys = "=2.11.0" [target.'cfg(target_os = "linux")'.dependencies] -gio = { version = "=0.19.5", optional = true } -libsecret = { version = "=0.5.0", optional = true } -zbus = { version = "=4.3.1", optional = true } -zbus_polkit = { version = "=4.0.0", optional = true } +gio = "=0.19.5" +libsecret = "=0.5.0" +zbus = "=4.3.1" +zbus_polkit = "=4.0.0" diff --git a/apps/desktop/desktop_native/core/src/ipc/client.rs b/apps/desktop/desktop_native/core/src/ipc/client.rs deleted file mode 100644 index 6e24a74629..0000000000 --- a/apps/desktop/desktop_native/core/src/ipc/client.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; - -use interprocess::local_socket::{ - tokio::{prelude::*, Stream}, - GenericFilePath, ToFsName, -}; -use log::{error, info}; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - time::sleep, -}; - -use crate::ipc::NATIVE_MESSAGING_BUFFER_SIZE; - -pub async fn connect( - path: PathBuf, - send: tokio::sync::mpsc::Sender, - mut recv: tokio::sync::mpsc::Receiver, -) { - // Keep track of connection failures to make sure we don't leave the process as a zombie - let mut connection_failures = 0; - - loop { - match connect_inner(&path, &send, &mut recv).await { - Ok(()) => return, - Err(e) => { - connection_failures += 1; - if connection_failures >= 20 { - error!("Failed to connect to IPC server after 20 attempts: {e}"); - return; - } - - error!("Failed to connect to IPC server: {e}"); - } - } - - sleep(Duration::from_secs(5)).await; - } -} - -async fn connect_inner( - path: &Path, - send: &tokio::sync::mpsc::Sender, - recv: &mut tokio::sync::mpsc::Receiver, -) -> Result<(), Box> { - info!("Attempting to connect to {}", path.display()); - - let name = path.as_os_str().to_fs_name::()?; - let mut conn = Stream::connect(name).await?; - - info!("Connected to {}", path.display()); - - // This `connected` and the latter `disconnected` messages are the only ones that - // are sent from the Rust IPC code and not just forwarded from the desktop app. - // As it's only two, we hardcode the JSON values to avoid pulling in a JSON library. - send.send("{\"command\":\"connected\"}".to_owned()).await?; - - let mut buffer = vec![0; NATIVE_MESSAGING_BUFFER_SIZE]; - - // Listen to IPC messages - loop { - tokio::select! { - // Forward messages to the IPC server - msg = recv.recv() => { - match msg { - Some(msg) => { - conn.write_all(msg.as_bytes()).await?; - } - None => { - info!("Client channel closed"); - break; - }, - } - }, - - // Forward messages from the IPC server - res = conn.read(&mut buffer[..]) => { - match res { - Err(e) => { - error!("Error reading from IPC server: {e}"); - break; - } - Ok(0) => { - info!("Connection closed"); - break; - } - Ok(n) => { - let message = String::from_utf8_lossy(&buffer[..n]).to_string(); - send.send(message).await?; - } - } - } - } - } - - let _ = send.send("{\"command\":\"disconnected\"}".to_owned()).await; - - Ok(()) -} diff --git a/apps/desktop/desktop_native/core/src/ipc/mod.rs b/apps/desktop/desktop_native/core/src/ipc/mod.rs deleted file mode 100644 index 6117dab38c..0000000000 --- a/apps/desktop/desktop_native/core/src/ipc/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -pub mod client; -pub mod server; - -/// The maximum size of a message that can be sent over IPC. -/// According to the documentation, the maximum size sent to the browser is 1MB. -/// While the maximum size sent from the browser to the native messaging host is 4GB. -/// -/// Currently we are setting the maximum both ways to be 1MB. -/// -/// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side -/// https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging#native-messaging-host-protocol -pub const NATIVE_MESSAGING_BUFFER_SIZE: usize = 1024 * 1024; - -/// The maximum number of messages that can be buffered in a channel. -/// This number is more or less arbitrary and can be adjusted as needed, -/// but ideally the messages should be processed as quickly as possible. -pub const MESSAGE_CHANNEL_BUFFER: usize = 32; - -/// Resolve the path to the IPC socket. -pub fn path(name: &str) -> std::path::PathBuf { - #[cfg(target_os = "windows")] - { - // Use a unique IPC pipe //./pipe/xxxxxxxxxxxxxxxxx.app.bitwarden per user. - // Hashing prevents problems with reserved characters and file length limitations. - use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; - use sha2::Digest; - let home = dirs::home_dir().unwrap(); - let hash = sha2::Sha256::digest(home.as_os_str().as_encoded_bytes()); - let hash_b64 = URL_SAFE_NO_PAD.encode(hash.as_slice()); - - format!(r"\\.\pipe\{hash_b64}.app.{name}").into() - } - - #[cfg(target_os = "macos")] - { - let mut home = dirs::home_dir().unwrap(); - - // When running in an unsandboxed environment, path is: /Users// - // While running sandboxed, it's different: /Users//Library/Containers/com.bitwarden.desktop/Data - // - // We want to use App Groups in /Users//Library/Group Containers/LTZ2PFU5D6.com.bitwarden.desktop, - // so we need to remove all the components after the user. - // Note that we subtract 3 because the root directory is counted as a component (/, Users, ). - let num_components = home.components().count(); - for _ in 0..num_components - 3 { - home.pop(); - } - - home.join(format!( - "Library/Group Containers/LTZ2PFU5D6.com.bitwarden.desktop/tmp/app.{name}" - )) - } - - #[cfg(target_os = "linux")] - { - // On Linux, we use the user's cache directory. - let home = dirs::cache_dir().unwrap(); - let path_dir = home.join("com.bitwarden.desktop"); - - // The chache directory might not exist, so create it - let _ = std::fs::create_dir_all(&path_dir); - path_dir.join(format!("app.{name}")) - } -} diff --git a/apps/desktop/desktop_native/core/src/ipc/server.rs b/apps/desktop/desktop_native/core/src/ipc/server.rs deleted file mode 100644 index 053b432220..0000000000 --- a/apps/desktop/desktop_native/core/src/ipc/server.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::{error::Error, path::Path, vec}; - -use futures::TryFutureExt; - -use anyhow::Result; -use interprocess::local_socket::{tokio::prelude::*, GenericFilePath, ListenerOptions}; -use log::{error, info}; -use tokio::{ - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, - sync::{broadcast, mpsc}, -}; -use tokio_util::sync::CancellationToken; - -use super::{MESSAGE_CHANNEL_BUFFER, NATIVE_MESSAGING_BUFFER_SIZE}; - -#[derive(Debug)] -pub struct Message { - pub client_id: u32, - pub kind: MessageType, - // This value should be Some for MessageType::Message and None for the rest - pub message: Option, -} - -#[derive(Debug)] -pub enum MessageType { - Connected, - Disconnected, - Message, -} - -pub struct Server { - cancel_token: CancellationToken, - server_to_clients_send: broadcast::Sender, -} - -impl Server { - /// Create and start the IPC server without blocking. - /// - /// # Parameters - /// - /// - `name`: The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - /// - `client_to_server_send`: This [`mpsc::Sender`] will receive all the [`Message`]'s that the clients send to this server. - pub fn start( - path: &Path, - client_to_server_send: mpsc::Sender, - ) -> Result> { - // If the unix socket file already exists, we get an error when trying to bind to it. So we remove it first. - // Any processes that were using the old socket should remain connected to it but any new connections will use the new socket. - if !cfg!(windows) { - let _ = std::fs::remove_file(path); - } - - let name = path.as_os_str().to_fs_name::()?; - let opts = ListenerOptions::new().name(name); - let listener = opts.create_tokio()?; - - // This broadcast channel is used for sending messages to all connected clients, and so the sender - // will be stored in the server while the receiver will be cloned and passed to each client handler. - let (server_to_clients_send, server_to_clients_recv) = - broadcast::channel::(MESSAGE_CHANNEL_BUFFER); - - // This cancellation token allows us to cleanly stop the server and all the spawned - // tasks without having to wait on all the pending tasks finalizing first - let cancel_token = CancellationToken::new(); - - // Create the server and start listening for incoming connections - // in a separate task to avoid blocking the current task - let server = Server { - cancel_token: cancel_token.clone(), - server_to_clients_send, - }; - tokio::spawn(listen_incoming( - listener, - client_to_server_send, - server_to_clients_recv, - cancel_token, - )); - - Ok(server) - } - - /// Send a message over the IPC server to all the connected clients - /// - /// # Returns - /// - /// The number of clients that the message was sent to. Note that the number of messages - /// sent may be less than the number of connected clients if some clients disconnect while - /// the message is being sent. - pub fn send(&self, message: String) -> Result { - let sent = self.server_to_clients_send.send(message)?; - Ok(sent) - } - - /// Stop the IPC server. - pub fn stop(&self) { - self.cancel_token.cancel(); - } -} - -impl Drop for Server { - fn drop(&mut self) { - self.stop(); - } -} - -async fn listen_incoming( - listener: LocalSocketListener, - client_to_server_send: mpsc::Sender, - server_to_clients_recv: broadcast::Receiver, - cancel_token: CancellationToken, -) { - // We use a simple incrementing ID for each client - let mut next_client_id = 1_u32; - - loop { - tokio::select! { - _ = cancel_token.cancelled() => { - info!("IPC server cancelled."); - break; - }, - - // A new client connection has been established - msg = listener.accept() => { - match msg { - Ok(client_stream) => { - let client_id = next_client_id; - next_client_id += 1; - - let future = handle_connection( - client_stream, - client_to_server_send.clone(), - // We resubscribe to the receiver here so this task can have it's own copy - // Note that this copy will only receive messages sent after this point, - // but that is okay, realistically we don't want any messages before we get a chance - // to send the connected message to the client, which is done inside [`handle_connection`] - server_to_clients_recv.resubscribe(), - cancel_token.clone(), - client_id - ); - tokio::spawn(future.map_err(|e| { - error!("Error handling connection: {}", e) - })); - }, - Err(e) => { - error!("Error accepting connection: {}", e); - break; - }, - } - } - } - } -} - -async fn handle_connection( - mut client_stream: impl AsyncRead + AsyncWrite + Unpin, - client_to_server_send: mpsc::Sender, - mut server_to_clients_recv: broadcast::Receiver, - cancel_token: CancellationToken, - client_id: u32, -) -> Result<(), Box> { - client_to_server_send - .send(Message { - client_id, - kind: MessageType::Connected, - message: None, - }) - .await?; - - let mut buf = vec![0u8; NATIVE_MESSAGING_BUFFER_SIZE]; - - loop { - tokio::select! { - _ = cancel_token.cancelled() => { - info!("Client {client_id} cancelled."); - break; - }, - - // Forward messages to the IPC clients - msg = server_to_clients_recv.recv() => { - match msg { - Ok(msg) => { - client_stream.write_all(msg.as_bytes()).await?; - }, - Err(e) => { - info!("Error reading message: {}", e); - break; - } - } - }, - - // Forwards messages from the IPC clients to the server - // Note that we also send connect and disconnect events so that - // the server can keep track of multiple clients - result = client_stream.read(&mut buf) => { - match result { - Err(e) => { - info!("Error reading from client {client_id}: {e}"); - - client_to_server_send.send(Message { - client_id, - kind: MessageType::Disconnected, - message: None, - }).await?; - break; - }, - Ok(0) => { - info!("Client {client_id} disconnected."); - - client_to_server_send.send(Message { - client_id, - kind: MessageType::Disconnected, - message: None, - }).await?; - break; - }, - Ok(size) => { - let msg = std::str::from_utf8(&buf[..size])?; - - client_to_server_send.send(Message { - client_id, - kind: MessageType::Message, - message: Some(msg.to_string()), - }).await?; - }, - - } - } - } - } - - Ok(()) -} diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index 3132c56f7f..d23a285b4a 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -1,13 +1,7 @@ -#[cfg(feature = "sys")] pub mod biometric; -#[cfg(feature = "sys")] pub mod clipboard; pub mod crypto; pub mod error; -pub mod ipc; -#[cfg(feature = "sys")] pub mod password; -#[cfg(feature = "sys")] pub mod process_isolation; -#[cfg(feature = "sys")] pub mod powermonitor; diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index 6fb710b067..942ccdba21 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -16,10 +16,8 @@ manual_test = [] [dependencies] anyhow = "=1.0.86" desktop_core = { path = "../core" } -napi = { version = "=2.16.7", features = ["async"] } -napi-derive = "=2.16.6" -tokio = { version = "1.38.0" } -tokio-util = "0.7.11" +napi = { version = "=2.16.6", features = ["async"] } +napi-derive = "=2.16.5" [build-dependencies] napi-build = "=2.1.3" diff --git a/apps/desktop/desktop_native/napi/build.js b/apps/desktop/desktop_native/napi/build.js new file mode 100644 index 0000000000..6c92dbad1b --- /dev/null +++ b/apps/desktop/desktop_native/napi/build.js @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const child_process = require("child_process"); +const process = require("process"); + +let targets = []; +switch (process.platform) { + case "win32": + targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"]; + break; + + case "darwin": + targets = ["x86_64-apple-darwin", "aarch64-apple-darwin"]; + break; + + default: + targets = ['x86_64-unknown-linux-musl']; + process.env["PKG_CONFIG_ALLOW_CROSS"] = "1"; + process.env["PKG_CONFIG_ALL_STATIC"] = "1"; + break; +} + +targets.forEach(target => { + child_process.execSync(`npm run build -- --target ${target}`, {stdio: 'inherit'}); +}); diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index fe4ab59fd8..deaf6b8e57 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -51,33 +51,3 @@ export namespace powermonitors { export function onLock(callback: (err: Error | null, ) => any): Promise export function isLockMonitorAvailable(): Promise } -export namespace ipc { - export interface IpcMessage { - clientId: number - kind: IpcMessageType - message?: string - } - export const enum IpcMessageType { - Connected = 0, - Disconnected = 1, - Message = 2 - } - export class IpcServer { - /** - * Create and start the IPC server without blocking. - * - * @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - * @param callback This function will be called whenever a message is received from a client. - */ - static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise - /** Stop the IPC server. */ - stop(): void - /** - * Send a message over the IPC server to all the connected clients - * - * @return The number of clients that the message was sent to. Note that the number of messages - * actually received may be less, as some clients could disconnect before receiving the message. - */ - send(message: string): number - } -} diff --git a/apps/desktop/desktop_native/napi/index.js b/apps/desktop/desktop_native/napi/index.js index a0cfee8e1a..680f1302b9 100644 --- a/apps/desktop/desktop_native/napi/index.js +++ b/apps/desktop/desktop_native/napi/index.js @@ -206,4 +206,10 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -module.exports = nativeBinding +const { passwords, biometrics, clipboards, processisolations, powermonitors } = nativeBinding + +module.exports.passwords = passwords +module.exports.biometrics = biometrics +module.exports.clipboards = clipboards +module.exports.processisolations = processisolations +module.exports.powermonitors = powermonitors diff --git a/apps/desktop/desktop_native/napi/package.json b/apps/desktop/desktop_native/napi/package.json index 9f098c4965..70e472b395 100644 --- a/apps/desktop/desktop_native/napi/package.json +++ b/apps/desktop/desktop_native/napi/package.json @@ -3,7 +3,9 @@ "version": "0.1.0", "description": "", "scripts": { - "build": "napi build --platform --js false", + "build": "napi build --release --platform --js false", + "build:debug": "napi build --platform --js false", + "build:cross-platform": "node build.js", "test": "cargo test" }, "author": "", diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 838eb65124..dfdc316d25 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -189,103 +189,3 @@ pub mod powermonitors { } } - -#[napi] -pub mod ipc { - use desktop_core::ipc::server::{Message, MessageType}; - use napi::threadsafe_function::{ - ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode, - }; - - #[napi(object)] - pub struct IpcMessage { - pub client_id: u32, - pub kind: IpcMessageType, - pub message: Option, - } - - impl From for IpcMessage { - fn from(message: Message) -> Self { - IpcMessage { - client_id: message.client_id, - kind: message.kind.into(), - message: message.message, - } - } - } - - #[napi] - pub enum IpcMessageType { - Connected, - Disconnected, - Message, - } - - impl From for IpcMessageType { - fn from(message_type: MessageType) -> Self { - match message_type { - MessageType::Connected => IpcMessageType::Connected, - MessageType::Disconnected => IpcMessageType::Disconnected, - MessageType::Message => IpcMessageType::Message, - } - } - } - - #[napi] - pub struct IpcServer { - server: desktop_core::ipc::server::Server, - } - - #[napi] - impl IpcServer { - /// Create and start the IPC server without blocking. - /// - /// @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - /// @param callback This function will be called whenever a message is received from a client. - #[napi(factory)] - pub async fn listen( - name: String, - #[napi(ts_arg_type = "(error: null | Error, message: IpcMessage) => void")] - callback: ThreadsafeFunction, - ) -> napi::Result { - let (send, mut recv) = tokio::sync::mpsc::channel::(32); - tokio::spawn(async move { - while let Some(message) = recv.recv().await { - callback.call(Ok(message.into()), ThreadsafeFunctionCallMode::NonBlocking); - } - }); - - let path = desktop_core::ipc::path(&name); - - let server = desktop_core::ipc::server::Server::start(&path, send).map_err(|e| { - napi::Error::from_reason(format!( - "Error listening to server - Path: {path:?} - Error: {e} - {e:?}" - )) - })?; - - Ok(IpcServer { server }) - } - - /// Stop the IPC server. - #[napi] - pub fn stop(&self) -> napi::Result<()> { - self.server.stop(); - Ok(()) - } - - /// Send a message over the IPC server to all the connected clients - /// - /// @return The number of clients that the message was sent to. Note that the number of messages - /// actually received may be less, as some clients could disconnect before receiving the message. - #[napi] - pub fn send(&self, message: String) -> napi::Result { - self.server - .send(message) - .map_err(|e| { - napi::Error::from_reason(format!("Error sending message - Error: {e} - {e:?}")) - }) - // NAPI doesn't support u64 or usize, so we need to convert to u32 - .map(|u| u32::try_from(u).unwrap_or_default()) - } - } -} diff --git a/apps/desktop/desktop_native/proxy/Cargo.toml b/apps/desktop/desktop_native/proxy/Cargo.toml deleted file mode 100644 index 681c34c8ea..0000000000 --- a/apps/desktop/desktop_native/proxy/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -edition = "2021" -exclude = ["index.node"] -license = "GPL-3.0" -name = "desktop_proxy" -version = "0.0.0" -publish = false - -[dependencies] -anyhow = "=1.0.86" -desktop_core = { path = "../core", default-features = false } -futures = "0.3.30" -log = "0.4.21" -simplelog = "0.12.2" -tokio = { version = "1.38.0", features = ["io-std", "io-util", "macros", "rt"] } -tokio-util = { version = "0.7.11", features = ["codec"] } - -[target.'cfg(target_os = "macos")'.dependencies] -embed_plist = "1.2.2" diff --git a/apps/desktop/desktop_native/proxy/src/main.rs b/apps/desktop/desktop_native/proxy/src/main.rs deleted file mode 100644 index 776b363598..0000000000 --- a/apps/desktop/desktop_native/proxy/src/main.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::path::Path; - -use desktop_core::ipc::{MESSAGE_CHANNEL_BUFFER, NATIVE_MESSAGING_BUFFER_SIZE}; -use futures::{SinkExt, StreamExt}; -use log::*; -use tokio_util::codec::LengthDelimitedCodec; - -#[cfg(target_os = "macos")] -embed_plist::embed_info_plist!("../../../resources/info.desktop_proxy.plist"); - -fn init_logging(log_path: &Path, level: log::LevelFilter) { - use simplelog::{ColorChoice, CombinedLogger, Config, SharedLogger, TermLogger, TerminalMode}; - - let config = Config::default(); - - let mut loggers: Vec> = Vec::new(); - loggers.push(TermLogger::new( - level, - config.clone(), - TerminalMode::Stderr, - ColorChoice::Auto, - )); - - match std::fs::File::create(log_path) { - Ok(file) => { - loggers.push(simplelog::WriteLogger::new(level, config, file)); - } - Err(e) => { - eprintln!("Can't create file: {}", e); - } - } - - if let Err(e) = CombinedLogger::init(loggers) { - eprintln!("Failed to initialize logger: {}", e); - } -} - -/// Bitwarden IPC Proxy. -/// -/// This proxy allows browser extensions to communicate with a desktop application using Native -/// Messaging. This method allows an extension to send and receive messages through the use of -/// stdin/stdout streams. -/// -/// However, this also requires the browser to start the process in order for the communication to -/// occur. To overcome this limitation, we implement Inter-Process Communication (IPC) to establish -/// a stable communication channel between the proxy and the running desktop application. -/// -/// Browser extension <-[native messaging]-> proxy <-[ipc]-> desktop -/// -#[tokio::main(flavor = "current_thread")] -async fn main() { - let sock_path = desktop_core::ipc::path("bitwarden"); - - let log_path = { - let mut path = sock_path.clone(); - path.set_extension("bitwarden.log"); - path - }; - - init_logging(&log_path, LevelFilter::Info); - - info!("Starting Bitwarden IPC Proxy."); - - // Different browsers send different arguments when the app starts: - // - // Firefox: - // - The complete path to the app manifest. (in the form `/Users//Library/.../Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json`) - // - (in Firefox 55+) the ID (as given in the manifest.json) of the add-on that started it (in the form `{[UUID]}`). - // - // Chrome on Windows: - // - Origin of the extension that started it (in the form `chrome-extension://[ID]`). - // - Handle to the Chrome native window that started the app. - // - // Chrome on Linux and Mac: - // - Origin of the extension that started it (in the form `chrome-extension://[ID]`). - - let args: Vec<_> = std::env::args().skip(1).collect(); - info!("Process args: {:?}", args); - - // Setup two channels, one for sending messages to the desktop application (`out`) and one for receiving messages from the desktop application (`in`) - let (in_send, in_recv) = tokio::sync::mpsc::channel(MESSAGE_CHANNEL_BUFFER); - let (out_send, mut out_recv) = tokio::sync::mpsc::channel(MESSAGE_CHANNEL_BUFFER); - - let mut handle = tokio::spawn(desktop_core::ipc::client::connect( - sock_path, out_send, in_recv, - )); - - // Create a new codec for reading and writing messages from stdin/stdout. - let mut stdin = LengthDelimitedCodec::builder() - .max_frame_length(NATIVE_MESSAGING_BUFFER_SIZE) - .native_endian() - .new_read(tokio::io::stdin()); - let mut stdout = LengthDelimitedCodec::builder() - .max_frame_length(NATIVE_MESSAGING_BUFFER_SIZE) - .native_endian() - .new_write(tokio::io::stdout()); - - loop { - tokio::select! { - // IPC client has finished, so we should exit as well. - _ = &mut handle => { - break; - } - - // Receive messages from IPC and print to STDOUT. - msg = out_recv.recv() => { - match msg { - Some(msg) => { - debug!("OUT: {}", msg); - stdout.send(msg.into()).await.unwrap(); - } - None => { - info!("Channel closed, exiting."); - break; - } - } - }, - - // Listen to stdin and send messages to ipc processor. - msg = stdin.next() => { - match msg { - Some(Ok(msg)) => { - let m = String::from_utf8(msg.to_vec()).unwrap(); - debug!("IN: {}", m); - in_send.send(m).await.unwrap(); - } - Some(Err(e)) => { - error!("Error parsing input: {}", e); - break; - } - None => { - info!("Received EOF, exiting."); - break; - } - } - } - - } - } -} diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 02bb237776..b6572587fa 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -73,13 +73,6 @@ "CFBundleDevelopmentRegion": "en" }, "singleArchFiles": "node_modules/@bitwarden/desktop-napi/desktop_napi.darwin-*.node", - "extraFiles": [ - { - "from": "desktop_native/dist/desktop_proxy.${platform}-${arch}", - "to": "MacOS/desktop_proxy" - } - ], - "signIgnore": ["MacOS/desktop_proxy"], "target": ["dmg", "zip"] }, "win": { @@ -91,24 +84,16 @@ "from": "../../node_modules/regedit/vbs", "to": "regedit/vbs", "filter": ["**/*"] - } - ], - "extraFiles": [ + }, { - "from": "desktop_native/dist/desktop_proxy.${platform}-${arch}.exe", - "to": "desktop_proxy.exe" + "from": "resources/native-messaging.bat", + "to": "native-messaging.bat" } ] }, "linux": { "category": "Utility", "synopsis": "A secure and free password manager for all of your devices.", - "extraFiles": [ - { - "from": "desktop_native/dist/desktop_proxy.${platform}-${arch}", - "to": "desktop_proxy" - } - ], "target": ["deb", "freebsd", "rpm", "AppImage", "snap"], "desktop": { "Name": "Bitwarden", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 70a0d9cb8c..4562653978 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -18,7 +18,7 @@ "scripts": { "postinstall": "electron-rebuild", "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", - "build-native": "cd desktop_native && node build.js", + "build-native": "cd desktop_native/napi && npm run build", "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", diff --git a/apps/desktop/resources/entitlements.desktop_proxy.plist b/apps/desktop/resources/entitlements.desktop_proxy.plist deleted file mode 100644 index d5c7b8a2cc..0000000000 --- a/apps/desktop/resources/entitlements.desktop_proxy.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.application-groups - - LTZ2PFU5D6.com.bitwarden.desktop - - - diff --git a/apps/desktop/resources/entitlements.mas.plist b/apps/desktop/resources/entitlements.mas.plist index d42ade962c..5bfeba83a6 100644 --- a/apps/desktop/resources/entitlements.mas.plist +++ b/apps/desktop/resources/entitlements.mas.plist @@ -8,10 +8,6 @@ LTZ2PFU5D6 com.apple.security.app-sandbox - com.apple.security.application-groups - - LTZ2PFU5D6.com.bitwarden.desktop - com.apple.security.network.client com.apple.security.files.user-selected.read-write diff --git a/apps/desktop/resources/info.desktop_proxy.plist b/apps/desktop/resources/info.desktop_proxy.plist deleted file mode 100644 index d3c30e3e0e..0000000000 --- a/apps/desktop/resources/info.desktop_proxy.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - CFBundleIdentifier - com.bitwarden.desktop - - diff --git a/apps/desktop/resources/native-messaging.bat b/apps/desktop/resources/native-messaging.bat new file mode 100644 index 0000000000..45519250dd --- /dev/null +++ b/apps/desktop/resources/native-messaging.bat @@ -0,0 +1,7 @@ +@echo off +:: Helper script for starting the Native Messaging Proxy on Windows. + +cd ../ +set ELECTRON_RUN_AS_NODE=1 +set ELECTRON_NO_ATTACH_CONSOLE=1 +Bitwarden.exe resources/app.asar %* diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js index 08cff76e85..e128397e61 100644 --- a/apps/desktop/scripts/after-pack.js +++ b/apps/desktop/scripts/after-pack.js @@ -1,22 +1,14 @@ /* eslint-disable @typescript-eslint/no-var-requires, no-console */ require("dotenv").config(); -const child_process = require("child_process"); const path = require("path"); -const { flipFuses, FuseVersion, FuseV1Options } = require("@electron/fuses"); -const builder = require("electron-builder"); const fse = require("fs-extra"); exports.default = run; async function run(context) { console.log("## After pack"); - // console.log(context); - - if (context.packager.platform.nodeName !== "darwin" || context.arch === builder.Arch.universal) { - await addElectronFuses(context); - } - + console.log(context); if (context.electronPlatformName === "linux") { console.log("Creating memory-protection wrapper script"); const appOutDir = context.appOutDir; @@ -31,132 +23,4 @@ async function run(context) { fse.chmodSync(wrapperBin, "755"); console.log("Copied memory-protection wrapper script"); } - - if (["darwin", "mas"].includes(context.electronPlatformName)) { - const is_mas = context.electronPlatformName === "mas"; - const is_mas_dev = context.targets.some((e) => e.name === "mas-dev"); - - let id; - - // Only use the Bitwarden Identities on CI - if (process.env.GITHUB_ACTIONS === "true") { - if (is_mas) { - id = is_mas_dev - ? "E7C9978F6FBCE0553429185C405E61F5380BE8EB" - : "3rd Party Mac Developer Application: Bitwarden Inc"; - } else { - id = "Developer ID Application: 8bit Solutions LLC"; - } - // Locally, use the first valid code signing identity, unless CSC_NAME is set - } else if (process.env.CSC_NAME) { - id = process.env.CSC_NAME; - } else { - const identities = getIdentities(); - if (identities.length === 0) { - throw new Error("No valid identities found"); - } - id = identities[0].id; - } - - console.log(`Signing proxy binary before the main bundle, using identity '${id}'`); - - const appName = context.packager.appInfo.productFilename; - const appPath = `${context.appOutDir}/${appName}.app`; - const proxyPath = path.join(appPath, "Contents", "MacOS", "desktop_proxy"); - - const packageId = "com.bitwarden.desktop"; - const entitlementsName = "entitlements.desktop_proxy.plist"; - const entitlementsPath = path.join(__dirname, "..", "resources", entitlementsName); - child_process.execSync( - `codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${entitlementsPath} ${proxyPath}`, - ); - } -} - -// Partially based on electron-builder code: -// https://github.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/macPackager.ts -// https://github.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/codeSign/macCodeSign.ts - -const appleCertificatePrefixes = [ - "Developer ID Application:", - // "Developer ID Installer:", - // "3rd Party Mac Developer Application:", - // "3rd Party Mac Developer Installer:", - "Apple Development:", -]; - -function getIdentities() { - const ids = child_process - .execSync("/usr/bin/security find-identity -v -p codesigning") - .toString(); - - return ids - .split("\n") - .filter((line) => { - for (const prefix of appleCertificatePrefixes) { - if (line.includes(prefix)) { - return true; - } - } - return false; - }) - .map((line) => { - const split = line.trim().split(" "); - const id = split[1]; - const name = split.slice(2).join(" ").replace(/"/g, ""); - return { id, name }; - }); -} - -/** - * @param {import("electron-builder").AfterPackContext} context - */ -async function addElectronFuses(context) { - const platform = context.packager.platform.nodeName; - - const ext = { - darwin: ".app", - win32: ".exe", - linux: "", - }[platform]; - - const IS_LINUX = platform === "linux"; - const executableName = IS_LINUX - ? context.packager.appInfo.productFilename.toLowerCase().replace("-dev", "").replace(" ", "-") - : context.packager.appInfo.productFilename; // .toLowerCase() to accomodate Linux file named `name` but productFileName is `Name` -- Replaces '-dev' because on Linux the executable name is `name` even for the DEV builds - - const electronBinaryPath = path.join(context.appOutDir, `${executableName}${ext}`); - - console.log("## Adding fuses to the electron binary", electronBinaryPath); - - await flipFuses(electronBinaryPath, { - version: FuseVersion.V1, - strictlyRequireAllFuses: true, - resetAdHocDarwinSignature: platform === "darwin" && context.arch === builder.Arch.universal, - - // List of fuses and their default values is available at: - // https://www.electronjs.org/docs/latest/tutorial/fuses - - [FuseV1Options.RunAsNode]: false, - [FuseV1Options.EnableCookieEncryption]: true, - [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, - [FuseV1Options.EnableNodeCliInspectArguments]: false, - - // Currently, asar integrity is only implemented for macOS and Windows - // https://www.electronjs.org/docs/latest/tutorial/asar-integrity - // On macOS, it works by default, but on Windows it requires the - // asarIntegrity feature of electron-builder v25, currently in alpha - // https://github.com/electron-userland/electron-builder/releases/tag/v25.0.0-alpha.10 - [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: platform === "darwin", - - [FuseV1Options.OnlyLoadAppFromAsar]: true, - - // App refuses to open when enabled - [FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]: false, - - // To disable this, we should stop using the file:// protocol to load the app bundle - // This can be done by defining a custom app:// protocol and loading the bundle from there, - // but then any requests to the server will be blocked by CORS policy - [FuseV1Options.GrantFileProtocolExtraPrivileges]: true, - }); } diff --git a/apps/desktop/src/entry.ts b/apps/desktop/src/entry.ts index 3bb8446136..78fe51e8b9 100644 --- a/apps/desktop/src/entry.ts +++ b/apps/desktop/src/entry.ts @@ -1,33 +1,31 @@ -import { spawn } from "child_process"; -import * as path from "path"; +import { NativeMessagingProxy } from "./proxy/native-messaging-proxy"; -import { app } from "electron"; +// We need to import the other dependencies using `require` since `import` will +// generate `Error: Cannot find module 'electron'`. The cause of this error is +// due to native messaging setting the ELECTRON_RUN_AS_NODE env flag on windows +// which removes the electron module. This flag is needed for stdin/out to work +// properly on Windows. if ( - process.platform === "darwin" && process.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1) ) { - // If we're on MacOS, we need to support DuckDuckGo's IPC communication, - // which for the moment is launching the Bitwarden process. - // Ideally the browser would instead startup the desktop_proxy process - // when available, but for now we'll just launch it here. + if (process.platform === "darwin") { + // eslint-disable-next-line + const app = require("electron").app; - app.on("ready", () => { - app.dock.hide(); + app.on("ready", () => { + app.dock.hide(); + }); + } + + process.stdout.on("error", (e) => { + if (e.code === "EPIPE") { + process.exit(0); + } }); - const proc = spawn(path.join(process.execPath, "..", "desktop_proxy"), process.argv.slice(1), { - cwd: process.cwd(), - stdio: "inherit", - shell: false, - }); - - proc.on("exit", () => { - process.exit(0); - }); - proc.on("error", () => { - process.exit(1); - }); + const proxy = new NativeMessagingProxy(); + proxy.run(); } else { // eslint-disable-next-line const Main = require("./main").Main; diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 212e5b50ee..86d07440a7 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -220,7 +220,6 @@ export class Main { this.windowMain, app.getPath("userData"), app.getPath("exe"), - app.getAppPath(), ); this.desktopAutofillSettingsService = new DesktopAutofillSettingsService(stateProvider); @@ -266,21 +265,13 @@ export class Main { if (browserIntegrationEnabled || ddgIntegrationEnabled) { // Re-register the native messaging host integrations on startup, in case they are not present if (browserIntegrationEnabled) { - this.nativeMessagingMain - .generateManifests() - .catch((err) => this.logService.error("Error while generating manifests", err)); + this.nativeMessagingMain.generateManifests().catch(this.logService.error); } if (ddgIntegrationEnabled) { - this.nativeMessagingMain - .generateDdgManifests() - .catch((err) => this.logService.error("Error while generating DDG manifests", err)); + this.nativeMessagingMain.generateDdgManifests().catch(this.logService.error); } - this.nativeMessagingMain - .listen() - .catch((err) => - this.logService.error("Error while starting native message listener", err), - ); + this.nativeMessagingMain.listen(); } app.removeAsDefaultProtocolClient("bitwarden"); diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 036f35e61c..8c8404578b 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -1,34 +1,34 @@ import { existsSync, promises as fs } from "fs"; +import { Socket } from "net"; import { homedir, userInfo } from "os"; import * as path from "path"; import * as util from "util"; import { ipcMain } from "electron"; +import * as ipc from "node-ipc"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { ipc } from "@bitwarden/desktop-napi"; -import { isDev } from "../utils"; +import { getIpcSocketRoot } from "../proxy/ipc"; import { WindowMain } from "./window.main"; export class NativeMessagingMain { - private ipcServer: ipc.IpcServer | null; - private connected: number[] = []; + private connected: Socket[] = []; + private socket: any; constructor( private logService: LogService, private windowMain: WindowMain, private userPath: string, private exePath: string, - private appPath: string, ) { ipcMain.handle( "nativeMessaging.manifests", async (_event: any, options: { create: boolean }) => { if (options.create) { + this.listen(); try { - await this.listen(); await this.generateManifests(); } catch (e) { this.logService.error("Error generating manifests: " + e); @@ -51,8 +51,8 @@ export class NativeMessagingMain { "nativeMessaging.ddgManifests", async (_event: any, options: { create: boolean }) => { if (options.create) { + this.listen(); try { - await this.listen(); await this.generateDdgManifests(); } catch (e) { this.logService.error("Error generating duckduckgo manifests: " + e); @@ -72,46 +72,56 @@ export class NativeMessagingMain { ); } - async listen() { - if (this.ipcServer) { - this.ipcServer.stop(); + listen() { + ipc.config.id = "bitwarden"; + ipc.config.retry = 1500; + const ipcSocketRoot = getIpcSocketRoot(); + if (ipcSocketRoot != null) { + ipc.config.socketRoot = ipcSocketRoot; } - this.ipcServer = await ipc.IpcServer.listen("bitwarden", (error, msg) => { - switch (msg.kind) { - case ipc.IpcMessageType.Connected: { - this.connected.push(msg.clientId); - this.logService.info("Native messaging client " + msg.clientId + " has connected"); - break; - } - case ipc.IpcMessageType.Disconnected: { - const index = this.connected.indexOf(msg.clientId); - if (index > -1) { - this.connected.splice(index, 1); - } + ipc.serve(() => { + ipc.server.on("message", (data: any, socket: any) => { + this.socket = socket; + this.windowMain.win.webContents.send("nativeMessaging", data); + }); - this.logService.info("Native messaging client " + msg.clientId + " has disconnected"); - break; + ipcMain.on("nativeMessagingReply", (event, msg) => { + if (this.socket != null && msg != null) { + this.send(msg, this.socket); } - case ipc.IpcMessageType.Message: - this.windowMain.win.webContents.send("nativeMessaging", JSON.parse(msg.message)); - break; - } + }); + + ipc.server.on("connect", (socket: Socket) => { + this.connected.push(socket); + }); + + ipc.server.on("socket.disconnected", (socket, destroyedSocketID) => { + const index = this.connected.indexOf(socket); + if (index > -1) { + this.connected.splice(index, 1); + } + + this.socket = null; + ipc.log("client " + destroyedSocketID + " has disconnected!"); + }); }); - ipcMain.on("nativeMessagingReply", (event, msg) => { - if (msg != null) { - this.send(msg); - } - }); + ipc.server.start(); } stop() { - this.ipcServer?.stop(); + ipc.server.stop(); + // Kill all existing connections + this.connected.forEach((socket) => { + if (!socket.destroyed) { + socket.destroy(); + } + }); } - send(message: object) { - this.ipcServer?.send(JSON.stringify(message)); + send(message: object, socket: any) { + ipc.server.emit(socket, "message", message); } async generateManifests() { @@ -321,20 +331,11 @@ export class NativeMessagingMain { } private binaryPath() { - const ext = process.platform === "win32" ? ".exe" : ""; - - if (isDev()) { - return path.join( - this.appPath, - "..", - "desktop_native", - "target", - "debug", - `desktop_proxy${ext}`, - ); + if (process.platform === "win32") { + return path.join(path.dirname(this.exePath), "resources", "native-messaging.bat"); } - return path.join(path.dirname(this.exePath), `desktop_proxy${ext}`); + return this.exePath; } private getRegeditInstance() { diff --git a/apps/desktop/src/proxy/ipc.ts b/apps/desktop/src/proxy/ipc.ts new file mode 100644 index 0000000000..0160d6bf29 --- /dev/null +++ b/apps/desktop/src/proxy/ipc.ts @@ -0,0 +1,78 @@ +/* eslint-disable no-console */ +import { createHash } from "crypto"; +import { existsSync, mkdirSync } from "fs"; +import { homedir } from "os"; +import { join as path_join } from "path"; + +import * as ipc from "node-ipc"; + +export function getIpcSocketRoot(): string | null { + let socketRoot = null; + + switch (process.platform) { + case "darwin": { + const ipcSocketRootDir = path_join(homedir(), "tmp"); + if (!existsSync(ipcSocketRootDir)) { + mkdirSync(ipcSocketRootDir); + } + socketRoot = ipcSocketRootDir + "/"; + break; + } + case "win32": { + // Let node-ipc use a unique IPC pipe //./pipe/xxxxxxxxxxxxxxxxx.app.bitwarden per user. + // Hashing prevents problems with reserved characters and file length limitations. + socketRoot = createHash("sha1").update(homedir()).digest("hex") + "."; + } + } + return socketRoot; +} + +ipc.config.id = "proxy"; +ipc.config.retry = 1500; +ipc.config.logger = console.warn; // Stdout is used for native messaging +const ipcSocketRoot = getIpcSocketRoot(); +if (ipcSocketRoot != null) { + ipc.config.socketRoot = ipcSocketRoot; +} + +export default class IPC { + onMessage: (message: object) => void; + + private connected = false; + + connect() { + ipc.connectTo("bitwarden", () => { + ipc.of.bitwarden.on("connect", () => { + this.connected = true; + console.error("## connected to bitwarden desktop ##"); + + // Notify browser extension, connection is established to desktop application. + this.onMessage({ command: "connected" }); + }); + + ipc.of.bitwarden.on("disconnect", () => { + this.connected = false; + console.error("disconnected from world"); + + // Notify browser extension, no connection to desktop application. + this.onMessage({ command: "disconnected" }); + }); + + ipc.of.bitwarden.on("message", (message: any) => { + this.onMessage(message); + }); + + ipc.of.bitwarden.on("error", (err: any) => { + console.error("error", err); + }); + }); + } + + isConnected(): boolean { + return this.connected; + } + + send(json: object) { + ipc.of.bitwarden.emit("message", json); + } +} diff --git a/apps/desktop/src/proxy/native-messaging-proxy.ts b/apps/desktop/src/proxy/native-messaging-proxy.ts new file mode 100644 index 0000000000..f1b54a8201 --- /dev/null +++ b/apps/desktop/src/proxy/native-messaging-proxy.ts @@ -0,0 +1,23 @@ +import IPC from "./ipc"; +import NativeMessage from "./nativemessage"; + +// Proxy is a lightweight application which provides bi-directional communication +// between the browser extension and a running desktop application. +// +// Browser extension <-[native messaging]-> proxy <-[ipc]-> desktop +export class NativeMessagingProxy { + private ipc: IPC; + private nativeMessage: NativeMessage; + + constructor() { + this.ipc = new IPC(); + this.nativeMessage = new NativeMessage(this.ipc); + } + + run() { + this.ipc.connect(); + this.nativeMessage.listen(); + + this.ipc.onMessage = this.nativeMessage.send; + } +} diff --git a/apps/desktop/src/proxy/nativemessage.ts b/apps/desktop/src/proxy/nativemessage.ts new file mode 100644 index 0000000000..f7a32296f8 --- /dev/null +++ b/apps/desktop/src/proxy/nativemessage.ts @@ -0,0 +1,95 @@ +/* eslint-disable no-console */ +import IPC from "./ipc"; + +// Mostly based on the example from MDN, +// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging +export default class NativeMessage { + ipc: IPC; + + constructor(ipc: IPC) { + this.ipc = ipc; + } + + send(message: object) { + const messageBuffer = Buffer.from(JSON.stringify(message)); + + const headerBuffer = Buffer.alloc(4); + headerBuffer.writeUInt32LE(messageBuffer.length, 0); + + process.stdout.write(Buffer.concat([headerBuffer, messageBuffer])); + } + + listen() { + let payloadSize: number = null; + + // A queue to store the chunks as we read them from stdin. + // This queue can be flushed when `payloadSize` data has been read + const chunks: any = []; + + // Only read the size once for each payload + const sizeHasBeenRead = () => Boolean(payloadSize); + + // All the data has been read, reset everything for the next message + const flushChunksQueue = () => { + payloadSize = null; + chunks.splice(0); + }; + + const processData = () => { + // Create one big buffer with all all the chunks + const stringData = Buffer.concat(chunks); + console.error(stringData); + + // The browser will emit the size as a header of the payload, + // if it hasn't been read yet, do it. + // The next time we'll need to read the payload size is when all of the data + // of the current payload has been read (ie. data.length >= payloadSize + 4) + if (!sizeHasBeenRead()) { + try { + payloadSize = stringData.readUInt32LE(0); + } catch (e) { + console.error(e); + return; + } + } + + // If the data we have read so far is >= to the size advertised in the header, + // it means we have all of the data sent. + // We add 4 here because that's the size of the bytes that old the payloadSize + if (stringData.length >= payloadSize + 4) { + // Remove the header + const contentWithoutSize = stringData.slice(4, payloadSize + 4).toString(); + + // Reset the read size and the queued chunks + flushChunksQueue(); + + const json = JSON.parse(contentWithoutSize); + + // Forward to desktop application + this.ipc.send(json); + } + }; + + process.stdin.on("readable", () => { + // A temporary variable holding the nodejs.Buffer of each + // chunk of data read off stdin + let chunk = null; + + // Read all of the available data + // tslint:disable-next-line:no-conditional-assignment + while ((chunk = process.stdin.read()) !== null) { + chunks.push(chunk); + } + + try { + processData(); + } catch (e) { + console.error(e); + } + }); + + process.stdin.on("end", () => { + process.exit(0); + }); + } +} diff --git a/package-lock.json b/package-lock.json index 49346dbe88..15138a6696 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,6 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", "@microsoft/signalr": "8.0.7", @@ -5101,33 +5100,6 @@ "node": "*" } }, - "node_modules/@electron/fuses": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", - "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", - "dependencies": { - "chalk": "^4.1.1", - "fs-extra": "^9.0.1", - "minimist": "^1.2.5" - }, - "bin": { - "electron-fuses": "dist/bin.js" - } - }, - "node_modules/@electron/fuses/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@electron/get": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", diff --git a/package.json b/package.json index 872849eb85..391474243b 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,6 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", "@microsoft/signalr": "8.0.7",