You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lib.rs 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. mod contract;
  2. mod crypto;
  3. pub mod error;
  4. mod exchange_client;
  5. pub use contract::{
  6. add_participant, init_meeting, is_meet_participant, view_meet_participants,
  7. view_moderator_account,
  8. };
  9. use near_client::{
  10. core::{hash::CryptoHash, types::Nonce},
  11. prelude::*,
  12. };
  13. use base64::prelude::*;
  14. use common_api::api::{ApiResponse, Data, ExchangeMessage};
  15. use crypto::{decrypt, encrypt, secret};
  16. use error::ApiError;
  17. use exchange_client::{exchange, public_keys, receive};
  18. use futures::{select, FutureExt};
  19. use gloo_timers::future::TimeoutFuture;
  20. use itertools::Itertools;
  21. use js_sys::Promise;
  22. use log::{info, warn};
  23. use serde::{Deserialize, Serialize};
  24. use std::{collections::HashSet, str::FromStr, sync::Arc};
  25. use url::Url;
  26. use uuid::Uuid;
  27. use wasm_bindgen::prelude::*;
  28. type Result<T> = std::result::Result<T, ApiError>;
  29. #[wasm_bindgen(start)]
  30. pub fn start() -> Result<()> {
  31. console_error_panic_hook::set_once();
  32. console_log::init().unwrap();
  33. Ok(())
  34. }
  35. fn to_value<T: Serialize>(value: &T) -> JsValue {
  36. match serde_wasm_bindgen::to_value(value) {
  37. Ok(value) => value,
  38. Err(err) => err.into(),
  39. }
  40. }
  41. pub struct Handler {
  42. contract_id: AccountId,
  43. secret: [u8; 32],
  44. exchange_url: url::Url,
  45. rpc_url: url::Url,
  46. }
  47. impl Handler {
  48. pub fn add_path(&self, path: &str) -> url::Url {
  49. let mut url = self.exchange_url.clone();
  50. url.set_path(path);
  51. url
  52. }
  53. }
  54. #[derive(Clone, Debug, Serialize, Deserialize)]
  55. pub struct Meeting {
  56. pub meet_id: Uuid,
  57. pub transaction_id: CryptoHash,
  58. }
  59. #[wasm_bindgen]
  60. #[derive(Debug, Clone)]
  61. pub struct ProvisionerConfig {
  62. contract_id: AccountId,
  63. rpc_url: Url,
  64. exchange_url: Url,
  65. }
  66. #[wasm_bindgen]
  67. impl ProvisionerConfig {
  68. #[wasm_bindgen(constructor)]
  69. pub fn new(
  70. contract_id: String,
  71. rpc_url: &str,
  72. exchange_url: &str,
  73. ) -> Result<ProvisionerConfig> {
  74. let rpc_url = Url::from_str(rpc_url)
  75. .map_err(|err| ApiError::Other(format!("Bad rpc url, cause {err}")))?;
  76. let exchange_url = Url::from_str(exchange_url)
  77. .map_err(|err| ApiError::Other(format!("Bad exchange url, cause {err}")))?;
  78. let contract_id = AccountId::from_str(&contract_id).map_err(ApiError::from)?;
  79. Ok(Self {
  80. contract_id,
  81. rpc_url,
  82. exchange_url,
  83. })
  84. }
  85. }
  86. #[wasm_bindgen]
  87. pub struct KeyProvisioner {
  88. signer: Arc<Signer>,
  89. handler: Arc<Handler>,
  90. }
  91. impl KeyProvisioner {
  92. fn handler(&self) -> Arc<Handler> {
  93. Arc::clone(&self.handler)
  94. }
  95. fn signer(&self) -> Arc<Signer> {
  96. Arc::clone(&self.signer)
  97. }
  98. }
  99. #[wasm_bindgen]
  100. impl KeyProvisioner {
  101. #[wasm_bindgen(constructor)]
  102. pub fn new(
  103. keypair_str: String,
  104. nonce: Nonce,
  105. account_id: String,
  106. ProvisionerConfig {
  107. contract_id,
  108. rpc_url,
  109. exchange_url,
  110. }: ProvisionerConfig,
  111. ) -> Result<KeyProvisioner> {
  112. let signer = Arc::new(Signer::from_secret_str(
  113. &keypair_str,
  114. AccountId::from_str(&account_id)?,
  115. nonce,
  116. )?);
  117. let handler = Arc::new(Handler {
  118. contract_id,
  119. secret: secret(),
  120. exchange_url,
  121. rpc_url,
  122. });
  123. Ok(Self { signer, handler })
  124. }
  125. /// Initializes meeting by calling the contract and providing there a set of participants' keys
  126. ///
  127. /// Arguments
  128. ///
  129. /// - participants_set - The [`js_sys::Set`] represents hash set of participants' keys
  130. /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
  131. #[wasm_bindgen(js_name = initMeeting)]
  132. pub fn init_meeting(&self, participants_set: js_sys::Set, timeout_ms: u32) -> Promise {
  133. let handler = self.handler();
  134. let signer = self.signer();
  135. let init_meeting = async move {
  136. let participants: HashSet<AccountId> = participants_set
  137. .values()
  138. .into_iter()
  139. .map_ok(|it| {
  140. let Some(acc_id) = it.as_string() else {
  141. return Err(ApiError::Other("Set item type isn't a string".to_owned()));
  142. };
  143. AccountId::from_str(&acc_id).map_err(ApiError::from)
  144. })
  145. .flatten_ok()
  146. .try_collect()?;
  147. let (meet_id, transaction_id) = init_meeting(&handler, &signer, participants).await?;
  148. Ok(to_value(&Meeting {
  149. meet_id,
  150. transaction_id,
  151. }))
  152. };
  153. wasm_bindgen_futures::future_to_promise(async move {
  154. select! {
  155. meet = init_meeting.fuse() => meet,
  156. _ = TimeoutFuture::new(timeout_ms).fuse() => {
  157. Err(ApiError::CallTimeout("The initialization has been timed out".to_owned()).into())
  158. }
  159. }
  160. })
  161. }
  162. /// Sends participants' keys to the keys exchange server
  163. ///
  164. /// Arguments
  165. ///
  166. /// - meeting_id - The [`String`] that indicates ID of the meeting room
  167. /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
  168. #[wasm_bindgen(js_name = sendKeys)]
  169. pub fn send_keys(&self, meeting_id: String, timeout_ms: u32) -> Promise {
  170. let handler = self.handler();
  171. let signer = self.signer();
  172. let send_keys = async move {
  173. let meet_id = Uuid::from_str(&meeting_id).map_err(ApiError::from)?;
  174. let mut participants = view_meet_participants(&handler, meet_id)
  175. .await?
  176. .ok_or_else(|| {
  177. ApiError::InvalidSessionUuid(format!("Wrong Session ID: {meet_id}"))
  178. })?;
  179. info!("Get a meeting participants {participants:?}");
  180. while !participants.is_empty() {
  181. let infos =
  182. match public_keys(&handler, &signer, meet_id, participants.clone()).await {
  183. Ok(ApiResponse::Success(infos)) => infos,
  184. Ok(ApiResponse::Timeout) => {
  185. warn!("Timeout happens on a server-side, subscribed one more time");
  186. continue;
  187. }
  188. Err(err) => {
  189. return Err(ApiError::KeyExchange(format!(
  190. "The send keys operation has been failed, cause {err:?}"
  191. ))
  192. .into());
  193. }
  194. };
  195. // remove infos that is already processed
  196. for key in &infos {
  197. participants.remove(&key.account_id);
  198. }
  199. let messages = infos
  200. .into_iter()
  201. .map(|info| {
  202. let sk = signer.secret_key();
  203. let other_pk = info.public_key;
  204. let msg = encrypt(sk, other_pk, meet_id, &handler.secret)?;
  205. Ok::<ExchangeMessage, JsValue>(ExchangeMessage {
  206. account_id: info.account_id,
  207. message: Data {
  208. data: msg,
  209. moderator_pk: signer.public_key().to_owned(),
  210. },
  211. })
  212. })
  213. .try_collect()?;
  214. exchange(&handler, &signer, meet_id, messages).await?;
  215. }
  216. Ok(JsValue::from_str(
  217. &BASE64_STANDARD_NO_PAD.encode(handler.secret),
  218. ))
  219. };
  220. wasm_bindgen_futures::future_to_promise(async move {
  221. select! {
  222. aes_secret = send_keys.fuse() => aes_secret,
  223. _ = TimeoutFuture::new(timeout_ms).fuse() => {
  224. Err(ApiError::CallTimeout("The send keys operation has been timed out".to_owned()).into())
  225. }
  226. }
  227. })
  228. }
  229. /// Get participant's key from a server
  230. ///
  231. /// Arguments
  232. ///
  233. /// - meeting_id - The [`String`] that indicates ID of the meeting room
  234. /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
  235. #[wasm_bindgen(js_name = getKey)]
  236. pub fn get_key(&self, meeting_id: String, timeout_ms: u32) -> Promise {
  237. let handler = self.handler();
  238. let signer = self.signer();
  239. let get_key = async move {
  240. let meet_id = Uuid::from_str(&meeting_id).map_err(ApiError::from)?;
  241. loop {
  242. if let ApiResponse::Success(data) = receive(&handler, &signer, meet_id).await? {
  243. let secret =
  244. decrypt(signer.secret_key(), data.moderator_pk, meet_id, data.data)?;
  245. return Ok(JsValue::from_str(&BASE64_STANDARD_NO_PAD.encode(secret)));
  246. }
  247. }
  248. };
  249. wasm_bindgen_futures::future_to_promise(async move {
  250. select! {
  251. key = get_key.fuse() => key,
  252. _ = TimeoutFuture::new(timeout_ms).fuse() => {
  253. Err(ApiError::CallTimeout("The getting key operation has been timed out".to_owned()).into())
  254. }
  255. }
  256. })
  257. }
  258. }