1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[PM-7846] Implement a rust based native messaging proxy and IPC system

This commit is contained in:
Daniel García
2024-06-20 18:11:24 +02:00
parent d92e1b3eca
commit 0a83b8ddaf
26 changed files with 961 additions and 358 deletions

View File

@@ -180,14 +180,14 @@ jobs:
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: apps/desktop/desktop_native/napi
working-directory: apps/desktop/desktop_native
env:
PKG_CONFIG_ALLOW_CROSS: true
PKG_CONFIG_ALL_STATIC: true
TARGET: musl
run: |
rustup target add x86_64-unknown-linux-musl
npm run build:cross-platform
node build.js cross-platform
- name: Build application
run: npm run dist:lin
@@ -306,8 +306,8 @@ jobs:
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: apps/desktop/desktop_native/napi
run: npm run build:cross-platform
working-directory: apps/desktop/desktop_native
run: node build.js cross-platform
- name: Build & Sign (dev)
env:
@@ -589,8 +589,8 @@ jobs:
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: apps/desktop/desktop_native/napi
run: npm run build:cross-platform
working-directory: apps/desktop/desktop_native
run: node build.js cross-platform
- name: Build application (dev)
run: npm run build
@@ -753,8 +753,8 @@ jobs:
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: apps/desktop/desktop_native/napi
run: npm run build:cross-platform
working-directory: apps/desktop/desktop_native
run: node build.js cross-platform
- name: Build
if: steps.build-cache.outputs.cache-hit != 'true'
@@ -962,8 +962,8 @@ jobs:
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: apps/desktop/desktop_native/napi
run: npm run build:cross-platform
working-directory: apps/desktop/desktop_native
run: node build.js cross-platform
- name: Build
if: steps.build-cache.outputs.cache-hit != 'true'
@@ -1157,8 +1157,8 @@ jobs:
- name: Build Native Module
if: steps.cache.outputs.cache-hit != 'true'
working-directory: apps/desktop/desktop_native/napi
run: npm run build:cross-platform
working-directory: apps/desktop/desktop_native
run: node build.js cross-platform
- name: Build
if: steps.build-cache.outputs.cache-hit != 'true'

View File

@@ -88,9 +88,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
@@ -119,6 +119,12 @@ dependencies = [
"objc2",
]
[[package]]
name = "bytes"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cbc"
version = "0.1.2"
@@ -130,9 +136,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.99"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490"
[[package]]
name = "cfg-expr"
@@ -283,6 +289,15 @@ 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"
@@ -304,9 +319,13 @@ dependencies = [
"base64",
"cbc",
"core-foundation",
"dirs",
"futures",
"gio",
"interprocess",
"keytar",
"libsecret",
"log",
"rand",
"retry",
"scopeguard",
@@ -314,6 +333,8 @@ dependencies = [
"security-framework-sys",
"sha2",
"thiserror",
"tokio",
"tokio-util",
"typenum",
"widestring",
"windows",
@@ -328,6 +349,21 @@ dependencies = [
"napi",
"napi-build",
"napi-derive",
"tokio",
"tokio-util",
]
[[package]]
name = "desktop_proxy"
version = "0.0.0"
dependencies = [
"anyhow",
"desktop_core",
"futures",
"log",
"simplelog",
"tokio",
"tokio-util",
]
[[package]]
@@ -340,6 +376,27 @@ 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"
@@ -349,6 +406,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"
@@ -368,7 +431,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@@ -395,6 +458,21 @@ 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"
@@ -402,6 +480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
@@ -438,6 +517,12 @@ dependencies = [
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
@@ -450,9 +535,13 @@ 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",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
@@ -523,14 +612,14 @@ dependencies = [
"gobject-sys",
"libc",
"system-deps",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "glib"
version = "0.19.8"
version = "0.19.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b664491bc77ab55daa6714a592cdbe1a55e28abec09cb50e87689b90de456ff4"
checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44"
dependencies = [
"bitflags",
"futures-channel",
@@ -550,9 +639,9 @@ dependencies = [
[[package]]
name = "glib-macros"
version = "0.19.8"
version = "0.19.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d405205a405182f95e637710850a8e82f25ba01fdd6baebc82dabeaf0883376"
checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7"
dependencies = [
"heck",
"proc-macro-crate",
@@ -606,7 +695,7 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@@ -629,6 +718,27 @@ dependencies = [
"generic-array",
]
[[package]]
name = "interprocess"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67bafc2f5dbdad79a6d925649758d5472647b416028099f0b829d1b67fdd47d3"
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"
@@ -658,14 +768,24 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libloading"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
dependencies = [
"cfg-if",
"windows-targets 0.52.5",
]
[[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"
@@ -719,9 +839,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.21"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
@@ -745,10 +865,21 @@ dependencies = [
]
[[package]]
name = "napi"
version = "2.16.6"
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc300228808a0e6aea5a58115c82889240bcf8dab16fc25ad675b33e454b368"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "napi"
version = "2.16.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633e41b2b983cf7983134f0c50986ca524d0caf38a2c6fc893ea3fa2e26abb0c"
dependencies = [
"bitflags",
"ctor",
@@ -766,9 +897,9 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a"
[[package]]
name = "napi-derive"
version = "2.16.5"
version = "2.16.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0e034ddf6155192cf83f267ede763fe6c164dfa9971585436b16173718d94c4"
checksum = "70a8a778fd367b13c64232e58632514b795514ece491ce136d96e976d34a3eb8"
dependencies = [
"cfg-if",
"convert_case",
@@ -780,9 +911,9 @@ dependencies = [
[[package]]
name = "napi-derive-backend"
version = "1.0.67"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff2c00437f3b3266391eb5e6aa25d0029187daf5caf05b8e3271468fb5ae73e"
checksum = "b370b784440c65eb9001d839012eb912ee43e3a2d0361e2c30c13052372c39fe"
dependencies = [
"convert_case",
"once_cell",
@@ -824,6 +955,12 @@ 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"
@@ -834,6 +971,15 @@ 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"
@@ -935,9 +1081,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.0"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
dependencies = [
"memchr",
]
@@ -948,6 +1094,12 @@ 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 = "os_pipe"
version = "1.2.0"
@@ -955,7 +1107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@@ -1009,6 +1161,12 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[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.17"
@@ -1026,9 +1184,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.85"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
@@ -1081,6 +1239,12 @@ 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.2"
@@ -1090,6 +1254,17 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "regex"
version = "1.10.5"
@@ -1144,7 +1319,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@@ -1234,6 +1409,17 @@ dependencies = [
"digest",
]
[[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"
@@ -1250,10 +1436,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "syn"
version = "2.0.66"
name = "socket2"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "syn"
version = "2.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
dependencies = [
"proc-macro2",
"quote",
@@ -1288,7 +1484,7 @@ dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@@ -1320,6 +1516,39 @@ 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"
@@ -1327,8 +1556,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
"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]]
@@ -1517,7 +1776,7 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@@ -1573,6 +1832,15 @@ dependencies = [
"windows-targets 0.52.5",
]
[[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"

View File

@@ -1,3 +1,3 @@
[workspace]
resolver = "2"
members = ["napi", "core"]
members = ["napi", "core", "proxy"]

View File

@@ -0,0 +1,56 @@
/* 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) {
const targetArg = target ? `--target ${target}` : "";
return child_process.execSync(`npm run build --release -- ${targetArg}`, { stdio: 'inherit', cwd: path.join(__dirname, "napi") });
}
function buildProxyBin(target) {
const targetArg = target ? `--target ${target}` : "";
return child_process.execSync(`cargo build --release ${targetArg}`, {stdio: 'inherit', cwd: path.join(__dirname, "proxy")});
}
if (!crossPlatform) {
buildNapiModule();
buildProxyBin();
return;
}
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 => {
buildNapiModule(target);
buildProxyBin(target);
});
if (process.platform === "darwin") {
fs.mkdirSync(path.join(__dirname, "target", "darwin-universal"), { recursive: true });
let command = `lipo -create -output ${path.join(__dirname, "target", "darwin-universal", "desktop_proxy")} `;
targets.forEach(target => {
command += `${path.join(__dirname, "target", target, "release", "desktop_proxy")} `;
});
child_process.execSync(command, { stdio: 'inherit', cwd: __dirname});
}

View File

@@ -17,11 +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.0", features = ["tokio"] }
log = "0.4.21"
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]

View File

@@ -0,0 +1,91 @@
use std::time::Duration;
use interprocess::local_socket::{
tokio::{prelude::*, Stream},
GenericFilePath, ToFsName,
};
use log::{error, info};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
time::sleep,
};
pub async fn connect(
tx: tokio::sync::mpsc::Sender<String>,
mut rx: tokio::sync::mpsc::Receiver<String>,
) {
// 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(&tx, &mut rx).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(
tx: &tokio::sync::mpsc::Sender<String>,
rx: &mut tokio::sync::mpsc::Receiver<String>,
) -> Result<(), Box<dyn std::error::Error>> {
let path = super::path("bitwarden");
info!("Attempting to connect to {}", path.display());
let name = path.as_os_str().to_fs_name::<GenericFilePath>()?;
let mut conn = Stream::connect(name).await?;
info!("Connected to {}", path.display());
tx.send("{\"command\":\"connected\"}".to_owned()).await?;
let mut buffer = vec![0; 8192];
// Listen to IPC messages
loop {
tokio::select! {
// Send messages to the IPC server
msg = rx.recv() => {
match msg {
Some(msg) => {
conn.write_all(msg.as_bytes()).await?;
}
None => break,
}
},
// Read messages from the IPC server
res = conn.read(&mut buffer[..]) => {
match res {
Err(e) => {
error!("Error reading from IPC server: {e}");
tx.send("{\"command\":\"disconnected\"}".to_owned()).await?;
break;
}
Ok(0) => {
info!("Connection closed");
tx.send("{\"command\":\"disconnected\"}".to_owned()).await?;
break;
}
Ok(n) => {
let message = String::from_utf8_lossy(&buffer[..n]).to_string();
tx.send(message).await?;
}
}
}
}
}
Ok(())
}

View File

@@ -0,0 +1,27 @@
pub mod client;
pub mod server;
// TODO: We probably can find a better location for the IPC socket on Mac.
// One idea is to use App Groups if we have the entitlement:
// https://developer.apple.com/documentation/xcode/configuring-app-groups
// Then we can write the socket to /Users/<user>/Library/Group Containers/group.com.bitwarden.<our group name>/ipc.sock
// We might also be able to write to the Apps Container directory from the proxy binary:
// /Users/<user>/Library/Containers/com.bitwarden.desktop/Data
/// Resolve the path to the IPC socket.
pub fn path(name: &str) -> std::path::PathBuf {
let home = dirs::home_dir().unwrap();
if cfg!(windows) {
// Use a unique IPC pipe //./pipe/bitwarden.xxxxxxxxxxxxxxxxx.sock 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 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()
} else {
home.join("tmp").join(format!("app.{name}"))
}
}

View File

@@ -0,0 +1,190 @@
use std::error::Error;
use futures::{Sink, SinkExt, 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::Receiver,
};
use tokio_util::sync::CancellationToken;
#[derive(Debug)]
pub struct Message {
pub client_id: u32,
pub kind: MessageType,
pub message: String,
}
#[derive(Debug)]
pub enum MessageType {
Connected,
Disconnected,
Message,
}
pub struct Server {
cancel_token: CancellationToken,
tx_connections: tokio::sync::broadcast::Sender<String>,
}
impl Server {
pub fn start<T>(name: &str, tx: T) -> Result<Self, Box<dyn Error>>
where
T: Sink<Message> + Unpin + Send + Clone + 'static,
<T as Sink<Message>>::Error: std::error::Error + 'static,
{
let path = super::path(name);
// 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 will remain connected 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::<GenericFilePath>()?;
let opts = ListenerOptions::new().name(name);
let listener = opts.create_tokio()?;
let (tx_connections, rx_connections) = tokio::sync::broadcast::channel::<String>(32);
let cancel_token = CancellationToken::new();
let cancel_token2 = cancel_token.clone();
tokio::spawn(async move {
futures::pin_mut!(listener);
let mut next_client_id = 1_u32;
loop {
tokio::select! {
_ = cancel_token2.cancelled() => {
info!("IPC server cancelled.");
break;
},
msg = listener.accept() => {
match msg {
Ok(stream) => {
let client_id = next_client_id;
next_client_id += 1;
let rx_connections = rx_connections.resubscribe();
let tx = tx.clone();
let cancel_token_clone = cancel_token2.clone();
tokio::spawn(handle_connection(stream, tx, rx_connections, cancel_token_clone, client_id).map_err(|e| {
error!("Error handling connection: {}", e)
}));
},
Err(e) => {
error!("Error reading message: {}", e);
break;
},
}
}
}
}
});
Ok(Self {
cancel_token,
tx_connections,
})
}
pub fn send(&self, message: String) -> Result<()> {
self.tx_connections.send(message)?;
Ok(())
}
pub fn stop(&self) {
self.cancel_token.cancel();
}
}
impl Drop for Server {
fn drop(&mut self) {
self.stop();
}
}
async fn handle_connection<T>(
mut stream: impl AsyncRead + AsyncWrite + Unpin,
mut tx: T,
mut rx_connections: Receiver<String>,
cancel_token: CancellationToken,
client_id: u32,
) -> Result<(), Box<dyn Error>>
where
T: Sink<Message> + Unpin,
<T as Sink<Message>>::Error: std::error::Error + 'static,
{
tx.send(Message {
client_id,
kind: MessageType::Connected,
message: "Connected".to_owned(),
})
.await?;
let mut buf = vec![0u8; 8192];
loop {
tokio::select! {
_ = cancel_token.cancelled() => {
info!("Client {client_id} cancelled.");
break;
},
msg = rx_connections.recv() => {
match msg {
Ok(msg) => {
stream.write_all(msg.as_bytes()).await?;
},
Err(e) => {
info!("Error reading message: {}", e);
break;
}
}
},
result = stream.read(&mut buf) => {
match result {
Err(e) => {
info!("Error reading from client {client_id}: {e}");
tx.send(Message {
client_id,
kind: MessageType::Disconnected,
message: "Disconnected".to_owned(),
}).await?;
break;
},
Ok(0) => {
info!("Client {client_id} disconnected.");
tx.send(Message {
client_id,
kind: MessageType::Disconnected,
message: "Disconnected".to_owned(),
}).await?;
break;
},
Ok(size) => {
let msg = std::str::from_utf8(&buf[..size])?;
tx.send(Message {
client_id,
kind: MessageType::Message,
message: msg.to_string(),
}).await?;
},
}
}
}
}
Ok(())
}

View File

@@ -2,4 +2,5 @@ pub mod biometric;
pub mod clipboard;
pub mod crypto;
pub mod error;
pub mod ipc;
pub mod password;

View File

@@ -16,8 +16,10 @@ manual_test = []
[dependencies]
anyhow = "=1.0.86"
desktop_core = { path = "../core" }
napi = { version = "=2.16.6", features = ["async"] }
napi-derive = "=2.16.5"
napi = { version = "=2.16.7", features = ["async"] }
napi-derive = "=2.16.6"
tokio = { version = "1.38.0" }
tokio-util = "0.7.11"
[build-dependencies]
napi-build = "=2.1.3"

View File

@@ -1,24 +0,0 @@
/* 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'});
});

View File

@@ -41,3 +41,23 @@ export namespace clipboards {
export function read(): Promise<string>
export function write(text: string, password: boolean): Promise<void>
}
export namespace ipc {
export const enum IpcMessageType {
Connected = 0,
Disconnected = 1,
Message = 2
}
export class IpcMessage {
clientId: number
kind: IpcMessageType
message: string
}
export class IpcServer {
/** Create and start the IPC server. */
static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): IpcServer
/** Stop the IPC server. */
stop(): void
/** Send a message over the IPC server. */
send(message: string): void
}
}

View File

@@ -206,8 +206,9 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { passwords, biometrics, clipboards } = nativeBinding
const { passwords, biometrics, clipboards, ipc } = nativeBinding
module.exports.passwords = passwords
module.exports.biometrics = biometrics
module.exports.clipboards = clipboards
module.exports.ipc = ipc

View File

@@ -5,7 +5,6 @@
"scripts": {
"build": "napi build --release --platform --js false",
"build:debug": "napi build --platform --js false",
"build:cross-platform": "node build.js",
"test": "cargo test"
},
"author": "",

View File

@@ -142,3 +142,89 @@ pub mod clipboards {
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
}
#[napi]
pub mod ipc {
use desktop_core::ipc::server::{Message, MessageType};
use napi::threadsafe_function::{
ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode,
};
use tokio_util::sync::PollSender;
#[napi]
pub struct IpcMessage {
pub client_id: u32,
pub kind: IpcMessageType,
pub message: String,
}
impl From<Message> 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<MessageType> 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.
#[napi(factory)]
pub fn listen(
name: String,
#[napi(ts_arg_type = "(error: null | Error, message: IpcMessage) => void")]
callback: ThreadsafeFunction<IpcMessage, ErrorStrategy::CalleeHandled>,
) -> napi::Result<Self> {
let (tx, mut rx) = tokio::sync::mpsc::channel::<Message>(32);
tokio::spawn(async move {
while let Some(message) = rx.recv().await {
callback.call(Ok(message.into()), ThreadsafeFunctionCallMode::NonBlocking);
}
});
let server = desktop_core::ipc::server::Server::start(&name, PollSender::new(tx))
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
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.
#[napi]
pub fn send(&self, message: String) -> napi::Result<()> {
self.server
.send(message)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
}
}

View File

@@ -0,0 +1,16 @@
[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" }
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"] }

View File

@@ -0,0 +1,102 @@
use std::fs::File;
use futures::{SinkExt, StreamExt};
use log::*;
use tokio_util::codec::LengthDelimitedCodec;
fn init_logging() {
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode, WriteLogger};
let exe = std::env::current_exe().unwrap();
let parent = exe.parent().unwrap();
let level = LevelFilter::Info;
let config = Config::default();
let log_file = File::create(parent.join("proxy.log")).expect("Can't create file");
CombinedLogger::init(vec![
WriteLogger::new(level, config.clone(), log_file),
TermLogger::new(level, config, TerminalMode::Stderr, ColorChoice::Auto),
])
.unwrap();
}
/// 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() {
init_logging();
info!("Starting Bitwarden IPC Proxy.");
// Setup two channels, one for sending messages to the desktop application and one for receiving messages
let (in_tx, in_rx) = tokio::sync::mpsc::channel(32);
let (out_tx, mut out_rx) = tokio::sync::mpsc::channel(32);
let mut handle = tokio::spawn(desktop_core::ipc::client::connect(out_tx, in_rx));
// Create a new codec for reading and writing messages from stdin/stdout.
let mut stdin = LengthDelimitedCodec::builder()
.max_frame_length(8192)
.native_endian()
.new_read(tokio::io::stdin());
let mut stdout = LengthDelimitedCodec::builder()
.max_frame_length(8192)
.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_rx.recv() => {
match msg {
Some(msg) => {
debug!("OUT: {}", msg);
stdout.send(msg.into()).await.unwrap();
}
None => {
// Channel closed, exit.
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_tx.send(m).await.unwrap();
}
Some(Err(e)) => {
// Unexpected error, exit.
error!("Error parsing input: {}", e);
break;
}
None => {
// EOF, exit.
break;
}
}
}
}
}
info!("Exiting.");
}

View File

@@ -85,8 +85,8 @@
"filter": ["**/*"]
},
{
"from": "resources/native-messaging.bat",
"to": "native-messaging.bat"
"from": "desktop_native/target/release/desktop_proxy",
"to": "desktop_proxy"
}
]
},

View File

@@ -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/napi && npm run build",
"build-native": "cd desktop_native && node build.js",
"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",

View File

@@ -1,7 +0,0 @@
@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 %*

View File

@@ -1,35 +1,4 @@
import { NativeMessagingProxy } from "./proxy/native-messaging-proxy";
import { Main } from "./main";
// 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.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1)
) {
if (process.platform === "darwin") {
// eslint-disable-next-line
const app = require("electron").app;
app.on("ready", () => {
app.dock.hide();
});
}
process.stdout.on("error", (e) => {
if (e.code === "EPIPE") {
process.exit(0);
}
});
const proxy = new NativeMessagingProxy();
proxy.run();
} else {
// eslint-disable-next-line
const Main = require("./main").Main;
const main = new Main();
main.bootstrap();
}
const main = new Main();
main.bootstrap();

View File

@@ -214,6 +214,7 @@ export class Main {
this.windowMain,
app.getPath("userData"),
app.getPath("exe"),
app.getAppPath(),
);
this.desktopAutofillSettingsService = new DesktopAutofillSettingsService(stateProvider);

View File

@@ -1,27 +1,27 @@
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 { getIpcSocketRoot } from "../proxy/ipc";
import { isDev } from "../utils";
import { WindowMain } from "./window.main";
export class NativeMessagingMain {
private connected: Socket[] = [];
private socket: any;
private ipcServer: ipc.IpcServer | null;
private connected: number[] = [];
constructor(
private logService: LogService,
private windowMain: WindowMain,
private userPath: string,
private exePath: string,
private appPath: string,
) {
ipcMain.handle(
"nativeMessaging.manifests",
@@ -73,55 +73,46 @@ export class NativeMessagingMain {
}
listen() {
ipc.config.id = "bitwarden";
ipc.config.retry = 1500;
const ipcSocketRoot = getIpcSocketRoot();
if (ipcSocketRoot != null) {
ipc.config.socketRoot = ipcSocketRoot;
if (this.ipcServer) {
this.ipcServer.stop();
}
ipc.serve(() => {
ipc.server.on("message", (data: any, socket: any) => {
this.socket = socket;
this.windowMain.win.webContents.send("nativeMessaging", data);
});
this.ipcServer = ipc.IpcServer.listen("bitwarden", (error, msg) => {
switch (msg.kind) {
case ipc.IpcMessageType.Connected:
this.connected.push(msg.clientId);
break;
case ipc.IpcMessageType.Disconnected: {
const index = this.connected.indexOf(msg.clientId);
if (index > -1) {
this.connected.splice(index, 1);
}
ipcMain.on("nativeMessagingReply", (event, msg) => {
if (this.socket != null && msg != null) {
this.send(msg, this.socket);
this.logService.info("client " + index + " has disconnected!");
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!");
});
case ipc.IpcMessageType.Message:
this.logService.debug("received nativeMessaging message: " + msg.message);
this.windowMain.win.webContents.send("nativeMessaging", JSON.parse(msg.message));
break;
}
});
ipc.server.start();
}
stop() {
ipc.server.stop();
// Kill all existing connections
this.connected.forEach((socket) => {
if (!socket.destroyed) {
socket.destroy();
ipcMain.on("nativeMessagingReply", (event, msg) => {
if (msg != null) {
this.send(msg);
}
});
}
send(message: object, socket: any) {
ipc.server.emit(socket, "message", message);
stop() {
this.ipcServer?.stop();
}
send(message: object) {
const msg = JSON.stringify(message);
this.logService.debug("sent nativeMessaging message of length", msg.length, message);
this.ipcServer?.send(msg);
}
async generateManifests() {
@@ -331,6 +322,10 @@ export class NativeMessagingMain {
}
private binaryPath() {
if (isDev()) {
return path.join(this.appPath, "..", "desktop_native", "target", "release", "desktop_proxy");
}
if (process.platform === "win32") {
return path.join(path.dirname(this.exePath), "resources", "native-messaging.bat");
}

View File

@@ -1,78 +0,0 @@
/* 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);
}
}

View File

@@ -1,23 +0,0 @@
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;
}
}

View File

@@ -1,95 +0,0 @@
/* 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);
});
}
}