use std::convert::{TryFrom, TryInto};
use std::panic;
use console_error_panic_hook::hook as panic_hook;
use ed25519_dalek::{PublicKey, Signature};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use crate::entry::{decode_entry as decode, sign_and_encode, Entry, EntrySigned, LogId, SeqNum};
use crate::hash::Hash;
use crate::identity::KeyPair as KeyPairNonWasm;
use crate::message::{
Message, MessageEncoded, MessageFields as MessageFieldsNonWasm, MessageValue,
};
macro_rules! jserr {
($l:expr) => {
$l.map_err::<JsValue, _>(|err| js_sys::Error::new(&format!("{}", err)).into())?
};
($l:expr, $err:expr) => {
$l.map_err::<JsValue, _>(|_| js_sys::Error::new(&format!("{:?}", $err)).into())?
};
}
#[wasm_bindgen(js_name = setWasmPanicHook)]
pub fn set_wasm_panic_hook() {
panic::set_hook(Box::new(panic_hook));
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct KeyPair(KeyPairNonWasm);
#[wasm_bindgen]
impl KeyPair {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self(KeyPairNonWasm::new())
}
#[wasm_bindgen(js_name = fromPrivateKey)]
pub fn from_private_key(private_key: String) -> Result<KeyPair, JsValue> {
let key_pair_inner = jserr!(KeyPairNonWasm::from_private_key_str(&private_key));
Ok(KeyPair(key_pair_inner))
}
#[wasm_bindgen(js_name = publicKey)]
pub fn public_key(&self) -> String {
hex::encode(self.0.public_key().to_bytes())
}
#[wasm_bindgen(js_name = privateKey)]
pub fn private_key(&self) -> String {
hex::encode(self.0.private_key().to_bytes())
}
#[wasm_bindgen]
pub fn sign(&self, message: String) -> String {
let signature = self.0.sign(&message.as_bytes());
hex::encode(signature.to_bytes())
}
pub(crate) fn as_inner(&self) -> &KeyPairNonWasm {
&self.0
}
}
#[wasm_bindgen(js_name = verifySignature)]
pub fn verify_signature(
public_key: String,
message: String,
signature: String,
) -> Result<JsValue, JsValue> {
let public_key_bytes = jserr!(hex::decode(public_key));
let message_bytes = message.as_bytes();
let signature_bytes = jserr!(hex::decode(signature));
let public_key = jserr!(PublicKey::from_bytes(&public_key_bytes));
let signature = jserr!(Signature::try_from(&signature_bytes[..]));
match KeyPairNonWasm::verify(&public_key, &message_bytes, &signature) {
Ok(_) => Ok(JsValue::TRUE),
Err(_) => Ok(JsValue::FALSE),
}
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct MessageFields(MessageFieldsNonWasm);
#[wasm_bindgen]
impl MessageFields {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self(MessageFieldsNonWasm::new())
}
#[wasm_bindgen]
pub fn add(&mut self, name: String, value_type: String, value: JsValue) -> Result<(), JsValue> {
match &value_type[..] {
"str" => {
let value_str = jserr!(value.as_string().ok_or("Invalid string value"));
jserr!(self.0.add(&name, MessageValue::Text(value_str)));
Ok(())
}
"bool" => {
let value_bool = jserr!(value.as_bool().ok_or("Invalid boolean value"));
jserr!(self.0.add(&name, MessageValue::Boolean(value_bool)));
Ok(())
}
"int" => {
let value_int = jserr!(value.as_f64().ok_or("Invalid integer value")) as i64;
jserr!(self.0.add(&name, MessageValue::Integer(value_int)));
Ok(())
}
"float" => {
let value_float = jserr!(value.as_f64().ok_or("Invalid float value"));
jserr!(self.0.add(&name, MessageValue::Float(value_float)));
Ok(())
}
"relation" => {
let value_str = jserr!(value.as_string().ok_or("Invalid string value"));
let hash = jserr!(Hash::new(&value_str));
jserr!(self.0.add(&name, MessageValue::Relation(hash)));
Ok(())
}
_ => Err(js_sys::Error::new("Unknown type value").into()),
}
}
#[wasm_bindgen]
pub fn remove(&mut self, name: String) -> Result<(), JsValue> {
jserr!(self.0.remove(&name));
Ok(())
}
#[wasm_bindgen]
pub fn get(&mut self, name: String) -> Result<JsValue, JsValue> {
match self.0.get(&name) {
Some(MessageValue::Boolean(value)) => Ok(JsValue::from_bool(value.to_owned())),
Some(MessageValue::Text(value)) => Ok(JsValue::from_str(value)),
Some(MessageValue::Relation(value)) => Ok(JsValue::from_str(&value.as_str())),
Some(MessageValue::Float(value)) => Ok(JsValue::from_f64(value.to_owned())),
Some(MessageValue::Integer(value)) => {
let converted: i32 = jserr!(value.to_owned().try_into());
Ok(converted.into())
}
None => Ok(JsValue::NULL),
}
}
#[wasm_bindgen(js_name = length)]
pub fn len(&self) -> usize {
self.0.len()
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
format!("{:?}", self)
}
}
#[wasm_bindgen(js_name = encodeCreateMessage)]
pub fn encode_create_message(
schema_hash: String,
fields: MessageFields,
) -> Result<String, JsValue> {
let schema = jserr!(Hash::new(&schema_hash));
let message = jserr!(Message::new_create(schema, fields.0));
let message_encoded = jserr!(MessageEncoded::try_from(&message));
Ok(message_encoded.as_str().to_owned())
}
#[wasm_bindgen(js_name = encodeUpdateMessage)]
pub fn encode_update_message(
instance_id: String,
schema_hash: String,
fields: MessageFields,
) -> Result<String, JsValue> {
let instance = jserr!(Hash::new(&instance_id));
let schema = jserr!(Hash::new(&schema_hash));
let message = jserr!(Message::new_update(schema, instance, fields.0));
let message_encoded = jserr!(MessageEncoded::try_from(&message));
Ok(message_encoded.as_str().to_owned())
}
#[wasm_bindgen(js_name = encodeDeleteMessage)]
pub fn encode_delete_message(instance_id: String, schema_hash: String) -> Result<String, JsValue> {
let instance = jserr!(Hash::new(&instance_id));
let schema = jserr!(Hash::new(&schema_hash));
let message = jserr!(Message::new_delete(schema, instance));
let message_encoded = jserr!(MessageEncoded::try_from(&message));
Ok(message_encoded.as_str().to_owned())
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SignEncodeEntryResult {
pub entry_encoded: String,
pub entry_hash: String,
pub message_hash: String,
}
#[wasm_bindgen(js_name = signEncodeEntry)]
pub fn sign_encode_entry(
key_pair: &KeyPair,
encoded_message: String,
entry_skiplink_hash: Option<String>,
entry_backlink_hash: Option<String>,
seq_num: i32,
log_id: i32,
) -> Result<JsValue, JsValue> {
let skiplink_hash = match entry_skiplink_hash {
Some(hash) => Some(jserr!(Hash::new(&hash))),
None => None,
};
let backlink_hash = match entry_backlink_hash {
Some(hash) => Some(jserr!(Hash::new(&hash))),
None => None,
};
let seq_num = jserr!(SeqNum::new(seq_num.into()));
let message_encoded = jserr!(MessageEncoded::new(&encoded_message));
let message = jserr!(Message::try_from(&message_encoded));
let entry = jserr!(Entry::new(
&LogId::new(log_id.into()),
Some(&message),
skiplink_hash.as_ref(),
backlink_hash.as_ref(),
&seq_num,
));
let entry_signed = jserr!(sign_and_encode(&entry, key_pair.as_inner()));
let result = jserr!(wasm_bindgen::JsValue::from_serde(&SignEncodeEntryResult {
entry_encoded: entry_signed.as_str().into(),
entry_hash: entry_signed.hash().as_str().into(),
message_hash: message_encoded.hash().as_str().into(),
}));
Ok(result)
}
#[wasm_bindgen(js_name = decodeEntry)]
pub fn decode_entry(
entry_encoded: String,
message_encoded: Option<String>,
) -> Result<JsValue, JsValue> {
let message_encoded = match message_encoded {
Some(msg) => {
let inner = jserr!(MessageEncoded::new(&msg));
Some(inner)
}
None => None,
};
let entry_signed = jserr!(EntrySigned::new(&entry_encoded));
let entry: Entry = jserr!(decode(&entry_signed, message_encoded.as_ref()));
let result = jserr!(wasm_bindgen::JsValue::from_serde(&entry));
Ok(result)
}