1use std::{error::Error, fmt};
4
5use wasm_bindgen::prelude::*;
6use wowlab_parsers::{
7 CryptoError, NodeKeypair, build_sign_message, keypair_from_base64, sha256_hex,
8 verify_signature_base64,
9};
10use wowlab_types::wasm::{WasmCommonError, js_set};
11
12#[derive(Debug)]
14#[non_exhaustive]
15pub struct WasmCryptoError {
16 source: CryptoError,
17}
18
19impl fmt::Display for WasmCryptoError {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 fmt::Display::fmt(&self.source, f)
22 }
23}
24
25impl Error for WasmCryptoError {
26 fn source(&self) -> Option<&(dyn Error + 'static)> {
27 Some(&self.source)
28 }
29}
30
31impl From<CryptoError> for WasmCryptoError {
32 fn from(source: CryptoError) -> Self {
33 Self { source }
34 }
35}
36
37impl From<WasmCryptoError> for JsValue {
38 fn from(error: WasmCryptoError) -> Self {
39 let js_error = js_sys::Error::new(&error.to_string());
40
41 js_error.set_name("DecodeError");
42
43 js_error.into()
44 }
45}
46
47#[wasm_bindgen(js_name = generateNodeKeypair)]
49pub fn wasm_generate_node_keypair() -> Result<JsValue, WasmCommonError> {
50 let keypair = NodeKeypair::generate();
51 let obj = js_sys::Object::new();
52
53 js_set(
54 &obj,
55 "privateKey",
56 &JsValue::from_str(&keypair.private_key_base64()),
57 );
58 js_set(
59 &obj,
60 "publicKey",
61 &JsValue::from_str(&keypair.public_key_base64()),
62 );
63
64 Ok(obj.into())
65}
66
67#[wasm_bindgen(js_name = signMessage)]
69pub fn wasm_sign_message(
70 private_key_base64: &str,
71 message: &str,
72) -> Result<String, WasmCryptoError> {
73 let keypair = keypair_from_base64(private_key_base64)?;
74
75 Ok(keypair.sign_base64(message.as_bytes()))
76}
77
78#[wasm_bindgen(js_name = verifySignature)]
79pub fn wasm_verify_signature(
80 public_key_base64: &str,
81 message: &str,
82 signature_base64: &str,
83) -> Result<bool, WasmCryptoError> {
84 match verify_signature_base64(public_key_base64, message.as_bytes(), signature_base64) {
85 Ok(()) => Ok(true),
86 Err(error) if error.is_verification_failed() => Ok(false),
87 Err(error) => Err(error.into()),
88 }
89}
90
91#[wasm_bindgen(js_name = buildSignMessage)]
92#[must_use]
93pub fn wasm_build_sign_message(
94 timestamp: u64,
95 method: &str,
96 host: &str,
97 path: &str,
98 body: &str,
99) -> String {
100 build_sign_message(timestamp, method, host, path, body.as_bytes())
101}
102
103#[wasm_bindgen(js_name = buildSignMessageBytes)]
104#[must_use]
105pub fn wasm_build_sign_message_bytes(
106 timestamp: u64,
107 method: &str,
108 host: &str,
109 path: &str,
110 body: &[u8],
111) -> String {
112 build_sign_message(timestamp, method, host, path, body)
113}
114
115#[wasm_bindgen(js_name = sha256Hex)]
116#[must_use]
117pub fn wasm_sha256_hex(data: &str) -> String {
118 sha256_hex(data.as_bytes())
119}
120
121#[cfg(test)]
122mod tests {
123 use std::error::Error as _;
124
125 use googletest::prelude::*;
126
127 use super::*;
128
129 #[gtest]
130 fn crypto_error_retains_parsers_and_base64_sources_until_javascript_conversion() -> Result<()> {
132 let error = wasm_sign_message("!!!", "message").err().or_fail()?;
133
134 let crypto_source = error.source().or_fail()?;
135
136 verify_true!(crypto_source.is::<CryptoError>())?;
137 let base64_source = crypto_source.source().or_fail()?;
138
139 verify_that!(
140 base64_source.to_string(),
141 eq("Invalid symbol 33, offset 0.")
142 )?;
143
144 verify_that!(
145 error.to_string(),
146 eq("invalid private key base64: Invalid symbol 33, offset 0.")
148 )
149 }
150}