diff --git a/.github/workflows/build-wasm-internal.yml b/.github/workflows/build-wasm-internal.yml new file mode 100644 index 000000000..04bcc43f8 --- /dev/null +++ b/.github/workflows/build-wasm-internal.yml @@ -0,0 +1,76 @@ +--- +name: Build @bitwarden/sdk-internal + +on: + pull_request: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + workflow_dispatch: + +defaults: + run: + shell: bash + working-directory: crates/bitwarden-wasm-internal + +jobs: + build: + name: Building @bitwarden/sdk-wasm-internal + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Node + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 + with: + node-version: 20 + registry-url: "https://npm.pkg.github.com" + cache: "npm" + + - name: Install dependencies + run: npm i -g binaryen + + - name: Install rust + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable + with: + toolchain: stable + targets: wasm32-unknown-unknown + + - name: Cache cargo registry + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + key: wasm-cargo-cache + + - name: Install wasm-bindgen-cli + run: cargo install wasm-bindgen-cli + + - name: Build + run: ./build.sh -r + + - name: Upload artifact + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: sdk-bitwarden-wasm-internal + path: ${{ github.workspace }}/languages/js/sdk-internal/* + if-no-files-found: error + + - name: Set version + if: ${{ github.ref == 'refs/heads/main' }} + # Fetches current version from registry and uses prerelease to bump it + run: | + npm version --no-git-tag-version $(npm view @bitwarden/sdk-internal@latest version) + npm version --no-git-tag-version prerelease + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + working-directory: languages/js/sdk-internal + + - name: Publish NPM + if: ${{ github.ref == 'refs/heads/main' }} + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + working-directory: languages/js/sdk-internal diff --git a/.prettierignore b/.prettierignore index 36c418776..16243942f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,9 @@ target languages/* +!languages/js +languages/js/* +!languages/js/sdk-internal +languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm.js schemas /crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts about.hbs diff --git a/Cargo.lock b/Cargo.lock index 27eddfa65..4e0d14119 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,9 +446,11 @@ dependencies = [ "sha2", "thiserror", "tokio", + "tsify-next", "uniffi", "uuid", "validator", + "wasm-bindgen", "wiremock", "zeroize", "zxcvbn", @@ -683,6 +685,20 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "bitwarden-wasm-internal" +version = "0.1.0" +dependencies = [ + "bitwarden", + "console_error_panic_hook", + "console_log", + "js-sys", + "log", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "blake2" version = "0.10.6" @@ -3509,6 +3525,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.210" @@ -4155,6 +4182,30 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tsify-next" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4a645dca4ee0800f5ab60ce166deba2db6a0315de795a2691e138a3d55d756" +dependencies = [ + "serde", + "serde-wasm-bindgen", + "tsify-next-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-next-macros" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d5c06f8a51d759bb58129e30b2631739e7e1e4579fad1f30ac09a6c88e488a6" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.79", +] + [[package]] name = "typenum" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 190341a7e..85dd5472a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,12 +49,16 @@ serde_qs = ">=0.12.0, <0.14" serde_repr = ">=0.1.12, <0.2" thiserror = ">=1.0.40, <2.0" tokio = { version = "1.36.0", features = ["macros"] } +tsify-next = { version = ">=0.5.4, <0.6", features = [ + "js", +], default-features = false } uniffi = "=0.28.1" uuid = { version = ">=1.3.3, <2.0", features = ["serde", "v4"] } validator = { version = "0.18.1", features = ["derive"] } -wasm-bindgen = { version = "0.2.91", features = ["serde-serialize"] } +wasm-bindgen = { version = ">=0.2.91, <0.3", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.41" + [workspace.lints.clippy] unused_async = "deny" unwrap_used = "deny" diff --git a/crates/bitwarden-core/Cargo.toml b/crates/bitwarden-core/Cargo.toml index f4b6ec423..d212afb7a 100644 --- a/crates/bitwarden-core/Cargo.toml +++ b/crates/bitwarden-core/Cargo.toml @@ -20,6 +20,7 @@ no-memory-hardening = [ ] # Disable memory hardening features uniffi = ["bitwarden-crypto/uniffi", "dep:uniffi"] # Uniffi bindings secrets = [] # Secrets manager API +wasm = ["dep:wasm-bindgen", "dep:tsify-next"] # WASM support [dependencies] base64 = ">=0.22.1, <0.23" @@ -44,8 +45,10 @@ thiserror = { workspace = true } uniffi = { workspace = true, optional = true, features = ["tokio"] } uuid = { workspace = true } validator = { workspace = true } +wasm-bindgen = { workspace = true, optional = true } zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] } zxcvbn = { version = ">=3.0.1, <4.0", optional = true } +tsify-next = { workspace = true, optional = true } [target.'cfg(not(target_arch="wasm32"))'.dependencies] # By default, we use rustls as the TLS stack and rust-platform-verifier to support user-installed root certificates diff --git a/crates/bitwarden-core/src/client/client_settings.rs b/crates/bitwarden-core/src/client/client_settings.rs index bd678d131..70e2e6839 100644 --- a/crates/bitwarden-core/src/client/client_settings.rs +++ b/crates/bitwarden-core/src/client/client_settings.rs @@ -19,6 +19,11 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr( + feature = "wasm", + derive(tsify_next::Tsify), + tsify(into_wasm_abi, from_wasm_abi) +)] pub struct ClientSettings { /// The identity url of the targeted Bitwarden instance. Defaults to `https://identity.bitwarden.com` pub identity_url: String, @@ -44,6 +49,11 @@ impl Default for ClientSettings { #[allow(non_camel_case_types)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, JsonSchema)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr( + feature = "wasm", + derive(tsify_next::Tsify), + tsify(into_wasm_abi, from_wasm_abi) +)] pub enum DeviceType { Android = 0, iOS = 1, @@ -66,6 +76,9 @@ pub enum DeviceType { VivaldiBrowser = 18, VivaldiExtension = 19, SafariExtension = 20, - SDK = 21, + Server = 22, + WindowsCLI = 23, + MacOsCLI = 24, + LinuxCLI = 25, } diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml new file mode 100644 index 000000000..df69015cf --- /dev/null +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "bitwarden-wasm-internal" +version = "0.1.0" +publish = false + +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true +keywords.workspace = true + +[lib] +crate-type = ["cdylib"] + +[dependencies] +bitwarden = { workspace = true, features = ["internal", "wasm"] } +console_error_panic_hook = "0.1.7" +console_log = { version = "1.0.0", features = ["color"] } +js-sys = "0.3.68" +log = "0.4.20" +serde_json = ">=1.0.96, <2.0" +wasm-bindgen = { version = "0.2.91", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.41" + +[lints] +workspace = true diff --git a/crates/bitwarden-wasm-internal/README.md b/crates/bitwarden-wasm-internal/README.md new file mode 100644 index 000000000..4db9847f5 --- /dev/null +++ b/crates/bitwarden-wasm-internal/README.md @@ -0,0 +1,25 @@ +# bitwarden-wasm-internal + +**Note:** This is only for internal use. Bitwarden will not provide any support for this crate. + +Requirements: + +- `wasm32-unknown-unknown` rust target. +- `wasm-bindgen-cli` installed. +- `binaryen` installed for `wasm-opt` and `wasm2js`. + +```bash +rustup target add wasm32-unknown-unknown +cargo install -f wasm-bindgen-cli +brew install binaryen +``` + +#### Build + +```bash +# dev +./build.sh + +# release +./build.sh -r +``` diff --git a/crates/bitwarden-wasm-internal/build.sh b/crates/bitwarden-wasm-internal/build.sh new file mode 100755 index 000000000..aa8426c59 --- /dev/null +++ b/crates/bitwarden-wasm-internal/build.sh @@ -0,0 +1,26 @@ +# Move to the root of the repository +cd "$(dirname "$0")" +cd ../../ + +if [ "$1" != "-r" ]; then + # Dev + cargo build -p bitwarden-wasm-internal --target wasm32-unknown-unknown + wasm-bindgen --target bundler --out-dir languages/js/sdk-internal ./target/wasm32-unknown-unknown/debug/bitwarden_wasm_internal.wasm + wasm-bindgen --target nodejs --out-dir languages/js/sdk-internal/node ./target/wasm32-unknown-unknown/debug/bitwarden_wasm_internal.wasm +else + # Release + cargo build -p bitwarden-wasm-internal --target wasm32-unknown-unknown --release + wasm-bindgen --target bundler --out-dir languages/js/sdk-internal ./target/wasm32-unknown-unknown/release/bitwarden_wasm_internal.wasm + wasm-bindgen --target nodejs --out-dir languages/js/sdk-internal/node ./target/wasm32-unknown-unknown/release/bitwarden_wasm_internal.wasm +fi + +# Format +npx prettier --write ./languages/js/sdk-internal + +# Optimize size +wasm-opt -Os ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm -o ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm +wasm-opt -Os ./languages/js/sdk-internal/node/bitwarden_wasm_internal_bg.wasm -o ./languages/js/sdk-internal/node/bitwarden_wasm_internal_bg.wasm + +# Transpile to JS +wasm2js ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm -o ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm.js +npx terser ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm.js -o ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm.js diff --git a/crates/bitwarden-wasm-internal/src/client.rs b/crates/bitwarden-wasm-internal/src/client.rs new file mode 100644 index 000000000..994af1fcf --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/client.rs @@ -0,0 +1,57 @@ +extern crate console_error_panic_hook; +use std::rc::Rc; + +use bitwarden::{Client, ClientSettings}; +use log::{set_max_level, Level}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +fn convert_level(level: LogLevel) -> Level { + match level { + LogLevel::Trace => Level::Trace, + LogLevel::Debug => Level::Debug, + LogLevel::Info => Level::Info, + LogLevel::Warn => Level::Warn, + LogLevel::Error => Level::Error, + } +} + +// Rc<...> is to avoid needing to take ownership of the Client during our async run_command +// function https://github.com/rustwasm/wasm-bindgen/issues/2195#issuecomment-799588401 +#[wasm_bindgen] +pub struct BitwardenClient(Rc); + +#[wasm_bindgen] +impl BitwardenClient { + #[wasm_bindgen(constructor)] + pub fn new(settings: Option, log_level: Option) -> Self { + console_error_panic_hook::set_once(); + let log_level = convert_level(log_level.unwrap_or(LogLevel::Info)); + if let Err(_e) = console_log::init_with_level(log_level) { + set_max_level(log_level.to_level_filter()) + } + + Self(Rc::new(Client::new(settings))) + } + + /// Test method, echoes back the input + pub fn echo(&self, msg: String) -> String { + msg + } + + /// Test method, calls http endpoint + pub async fn http_get(&self, url: String) -> Result { + let client = self.0.internal.get_http_client(); + let res = client.get(&url).send().await.map_err(|e| e.to_string())?; + + res.text().await.map_err(|e| e.to_string()) + } +} diff --git a/crates/bitwarden-wasm-internal/src/lib.rs b/crates/bitwarden-wasm-internal/src/lib.rs new file mode 100644 index 000000000..b79c47fca --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/lib.rs @@ -0,0 +1 @@ +mod client; diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 9fa5c0cb3..1d09377cf 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -40,6 +40,7 @@ secrets = [ "dep:bitwarden-sm", "dep:bitwarden-generators", ] # Secrets manager API +wasm = ["bitwarden-core/wasm"] # WASM support [dependencies] bitwarden-api-api = { workspace = true } diff --git a/languages/js/sdk-internal/.gitignore b/languages/js/sdk-internal/.gitignore new file mode 100644 index 000000000..ef69b9de9 --- /dev/null +++ b/languages/js/sdk-internal/.gitignore @@ -0,0 +1,6 @@ +bitwarden_wasm_internal_bg.js +bitwarden_wasm_internal_bg.wasm +bitwarden_wasm_internal_bg.wasm.d.ts +bitwarden_wasm_internal_bg.wasm.js +bitwarden_wasm_internal.d.ts +bitwarden_wasm_internal.js diff --git a/languages/js/sdk-internal/index.js b/languages/js/sdk-internal/index.js new file mode 100644 index 000000000..0525f7aa4 --- /dev/null +++ b/languages/js/sdk-internal/index.js @@ -0,0 +1,8 @@ +import { __wbg_set_wasm } from "./bitwarden_wasm_internal_bg.js"; + +// In order to support a fallback strategy for web we need to conditionally load the wasm file +export function init(wasm) { + __wbg_set_wasm(wasm); +} + +export * from "./bitwarden_wasm_internal_bg.js"; diff --git a/languages/js/sdk-internal/package.json b/languages/js/sdk-internal/package.json new file mode 100644 index 000000000..94b678141 --- /dev/null +++ b/languages/js/sdk-internal/package.json @@ -0,0 +1,24 @@ +{ + "name": "@bitwarden/sdk-internal", + "version": "0.1.0", + "files": [ + "bitwarden_wasm_internal_bg.js", + "bitwarden_wasm_internal_bg.wasm", + "bitwarden_wasm_internal_bg.wasm.d.ts", + "bitwarden_wasm_internal_bg.wasm.js", + "bitwarden_wasm_internal.d.ts", + "bitwarden_wasm_internal.js", + "index.js", + "node/bitwarden_wasm_internal.wasm", + "node/bitwarden_wasm_internal.wasm.d.ts", + "node/bitwarden_wasm_internal.d.ts", + "node/bitwarden_wasm_internal.js" + ], + "main": "node/bitwarden_wasm_internal.js", + "module": "index.js", + "types": "bitwarden_wasm_internal.d.ts", + "scripts": {}, + "sideEffects": [ + "./bitwarden_wasm_internal.js" + ] +}