interop/build/web/
wasm.rs

1// Wire
2// Copyright (C) 2022 Wire Swiss GmbH
3
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see http://www.gnu.org/licenses/.
16
17use crate::util::RunningProcess;
18use crate::TEST_SERVER_PORT;
19use color_eyre::eyre::Result;
20use std::net::SocketAddr;
21
22async fn find_wasm_file(deploy_path: &std::path::Path) -> Result<std::path::PathBuf> {
23    let wasm_base_path = deploy_path.to_path_buf();
24    let wasm_path = if wasm_base_path.exists() {
25        let mut wasm_dir = tokio::fs::read_dir(wasm_base_path.clone()).await?;
26        let mut maybe_wasm_path = None;
27        while let Some(entry) = wasm_dir.next_entry().await? {
28            log::debug!("wasm dir entry: {entry:?}");
29            if let Some(ext) = entry.path().extension() {
30                if ext == std::ffi::OsStr::new("wasm") {
31                    log::debug!("found");
32                    maybe_wasm_path = Some(entry);
33                }
34            }
35        }
36        maybe_wasm_path
37            .map(|entry| entry.path())
38            .unwrap_or_else(|| wasm_base_path.join(".not-found"))
39    } else {
40        wasm_base_path.join(".not-found")
41    };
42
43    Ok(wasm_path)
44}
45
46pub(crate) async fn build_wasm() -> Result<()> {
47    use sha2::{Digest, Sha256};
48    use tokio::process::Command;
49
50    let cwd = std::env::current_dir()?;
51
52    if cfg!(feature = "proteus") {
53        let spinner = RunningProcess::new("Building Cryptobox ESM bundle...", false);
54
55        Command::new("bun")
56            .args(["install"])
57            .current_dir(cwd.join("interop/src/build/web/cryptobox-esm"))
58            .stdout(std::process::Stdio::null())
59            .stderr(std::process::Stdio::null())
60            .status()
61            .await?;
62
63        Command::new("bun")
64            .args(["run", "build"])
65            .current_dir(cwd.join("interop/src/build/web/cryptobox-esm"))
66            .stdout(std::process::Stdio::null())
67            .stderr(std::process::Stdio::null())
68            .status()
69            .await?;
70
71        spinner.success("Cryptobox ESM bundle [OK]");
72    }
73
74    let mut spinner = RunningProcess::new("Building WASM bundle...", false);
75
76    let wasm_deploy_path = cwd.join("platforms/web");
77
78    let exe_path = std::env::current_exe()?;
79    let exe_folder = exe_path.parent().unwrap();
80    let wasm_cache_path = exe_folder.join(".wasm.cache");
81    let js_cache_path = exe_folder.join(".js.cache");
82
83    let wasm_path = find_wasm_file(&wasm_deploy_path).await?;
84    let js_path = wasm_deploy_path.join("corecrypto.js");
85
86    std::fs::copy(
87        cwd.join("crypto-ffi/bindings/js/test/index.html"),
88        wasm_deploy_path.join("index.html"),
89    )?;
90
91    if !wasm_path.exists() || !js_path.exists() {
92        spinner.update("WASM: No JS/WASM files found, rebuilding; Please wait...");
93    } else if wasm_cache_path.exists() && js_cache_path.exists() {
94        let wasm_hash = hex::decode(tokio::fs::read_to_string(wasm_cache_path.clone()).await?)?;
95        let js_hash = hex::decode(tokio::fs::read_to_string(js_cache_path.clone()).await?)?;
96
97        let mut hasher = Sha256::new();
98        hasher.update(tokio::fs::read(js_path.clone()).await?);
99        let js_current_hash = hasher.finalize();
100
101        let mut hasher = Sha256::new();
102        hasher.update(tokio::fs::read(wasm_path.clone()).await?);
103        let wasm_current_hash = hasher.finalize();
104
105        if js_current_hash[..] == js_hash && wasm_current_hash[..] == wasm_hash {
106            spinner.success("WASM: builds are identical - skipping build");
107            return Ok(());
108        }
109
110        spinner.update("WASM: Hashes differ, needs rebuild! Please wait...");
111    } else {
112        spinner.update("WASM: No cache file found, rebuilding to get a cache! Please wait...");
113    }
114
115    let mut cargo_args = vec!["make", "wasm"];
116    let mut bun_env = vec![];
117
118    if cfg!(feature = "proteus") {
119        spinner.update(
120            "`proteus` feature enabled. Building `core-crypto` with proteus support & enabling bun env BUILD_PROTEUS=1; Also building ESM bundle for Cryptobox",
121        );
122        cargo_args.push("--features");
123        cargo_args.push("proteus");
124        bun_env.push(("BUILD_PROTEUS", "1"));
125    }
126
127    Command::new("cargo")
128        .args(&cargo_args)
129        .current_dir(cwd.join("crypto-ffi"))
130        .stdout(std::process::Stdio::null())
131        .status()
132        .await?;
133
134    Command::new("bun")
135        .args(["run", "wdio"])
136        .envs(bun_env.clone())
137        .stdout(std::process::Stdio::null())
138        .stderr(std::process::Stdio::null())
139        .status()
140        .await?;
141
142    spinner.update("WASM: Computing new file hashes...");
143
144    let mut hasher = Sha256::new();
145    hasher.update(tokio::fs::read(js_path).await?);
146    let js_current_hash = hex::encode(hasher.finalize());
147
148    let wasm_path = find_wasm_file(&wasm_deploy_path).await?;
149    log::debug!("Found wasm file at {wasm_path:?}");
150
151    let mut hasher = Sha256::new();
152    hasher.update(tokio::fs::read(wasm_path).await?);
153    let wasm_current_hash = hex::encode(hasher.finalize());
154
155    log::debug!("Cache updated; CoreCrypto.wasm[{wasm_current_hash}] | corecrypto.js[{js_current_hash}]");
156    log::debug!("JS Cache Path: {js_cache_path:?} | WASM Cache Path: {wasm_cache_path:?}");
157
158    tokio::fs::write(js_cache_path, js_current_hash).await?;
159    tokio::fs::write(wasm_cache_path, wasm_current_hash).await?;
160
161    spinner.success("WASM bundle [OK]");
162
163    Ok(())
164}
165
166pub(crate) async fn spawn_http_server() -> Result<()> {
167    use warp::Filter as _;
168    let addr = SocketAddr::from(([0, 0, 0, 0], TEST_SERVER_PORT.parse()?));
169    let warp_filter_cc = warp::path("core-crypto").and(warp::fs::dir("platforms/web".to_string()));
170    let warp_filter_cbox =
171        warp::path("cryptobox").and(warp::fs::dir("interop/src/build/web/cryptobox-esm/dist".to_string()));
172
173    warp::serve(warp_filter_cc.or(warp_filter_cbox).boxed())
174        .bind(addr)
175        .await;
176
177    Ok(())
178}