123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- mod contract;
- mod crypto;
- pub mod error;
- mod exchange_client;
-
- pub use contract::{
- add_participant, init_meeting, is_meet_participant, view_meet_participants,
- view_moderator_account,
- };
-
- use common_api::api::{Data, ExchangeMessage};
- use crypto::{decrypt, encrypt, secret};
- use error::ApiError;
- use exchange_client::{exchange, public_keys, receive};
- use futures::{select, FutureExt};
- use gloo_timers::future::TimeoutFuture;
- use itertools::Itertools;
- use js_sys::Promise;
- use log::{info, warn};
- use near_primitives_core::{account::id::AccountId, hash::CryptoHash, types::Nonce};
- use near_rpc::client::Signer;
- use serde::{Deserialize, Serialize};
- use std::{collections::HashSet, str::FromStr, sync::Arc};
- use url::Url;
- use uuid::Uuid;
- use wasm_bindgen::prelude::*;
-
- type Result<T> = std::result::Result<T, ApiError>;
-
- #[wasm_bindgen(start)]
- pub fn start() -> Result<()> {
- console_error_panic_hook::set_once();
- console_log::init().unwrap();
- Ok(())
- }
-
- fn to_value<T: Serialize>(value: &T) -> JsValue {
- match serde_wasm_bindgen::to_value(value) {
- Ok(value) => value,
- Err(err) => err.into(),
- }
- }
-
- pub struct Handler {
- contract_id: AccountId,
- secret: [u8; 32],
- exchange_url: url::Url,
- rpc_url: url::Url,
- }
-
- impl Handler {
- pub fn add_path(&self, path: &str) -> url::Url {
- let mut url = self.exchange_url.clone();
- url.set_path(path);
- url
- }
- }
-
- #[derive(Clone, Debug, Serialize, Deserialize)]
- pub struct Meeting {
- pub meet_id: Uuid,
- pub transaction_id: CryptoHash,
- }
-
- #[wasm_bindgen]
- #[derive(Debug, Clone)]
- pub struct ProvisionerConfig {
- contract_id: AccountId,
- rpc_url: Url,
- exchange_url: Url,
- }
-
- #[wasm_bindgen]
- impl ProvisionerConfig {
- #[wasm_bindgen(constructor)]
- pub fn new(
- contract_id: String,
- rpc_url: &str,
- exchange_url: &str,
- ) -> Result<ProvisionerConfig> {
- let rpc_url = Url::from_str(rpc_url)
- .map_err(|err| ApiError::Other(format!("Bad rpc url, cause {err}")))?;
- let exchange_url = Url::from_str(exchange_url)
- .map_err(|err| ApiError::Other(format!("Bad exchange url, cause {err}")))?;
- let contract_id = AccountId::from_str(&contract_id).map_err(ApiError::from)?;
-
- Ok(Self {
- contract_id,
- rpc_url,
- exchange_url,
- })
- }
- }
-
- #[wasm_bindgen]
- pub struct KeyProvisioner {
- signer: Arc<Signer>,
- handler: Arc<Handler>,
- }
-
- impl KeyProvisioner {
- fn handler(&self) -> Arc<Handler> {
- Arc::clone(&self.handler)
- }
-
- fn signer(&self) -> Arc<Signer> {
- Arc::clone(&self.signer)
- }
- }
-
- #[wasm_bindgen]
- impl KeyProvisioner {
- #[wasm_bindgen(constructor)]
- pub fn new(
- keypair_str: String,
- nonce: Nonce,
- account_id: String,
- ProvisionerConfig {
- contract_id,
- rpc_url,
- exchange_url,
- }: ProvisionerConfig,
- ) -> Result<KeyProvisioner> {
- let signer = Arc::new(Signer::from_secret_str(
- &keypair_str,
- AccountId::from_str(&account_id)?,
- nonce,
- )?);
- let handler = Arc::new(Handler {
- contract_id,
- secret: secret(),
- exchange_url,
- rpc_url,
- });
-
- Ok(Self { signer, handler })
- }
-
- /// Initializes meeting by calling the contract and providing there a set of participants' keys
- ///
- /// Arguments
- ///
- /// - participants_set - The [`js_sys::Set`] represents hash set of participants' keys
- /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
- pub fn init_meeting(&self, participants_set: js_sys::Set, timeout_ms: u32) -> Promise {
- let handler = self.handler();
- let signer = self.signer();
-
- let init_meeting = async move {
- let participants: HashSet<AccountId> = participants_set
- .values()
- .into_iter()
- .map_ok(|it| {
- let Some(acc_id) = it.as_string() else {
- return Err(ApiError::Other("Set item type isn't a string".to_owned()));
- };
-
- AccountId::from_str(&acc_id).map_err(ApiError::from)
- })
- .flatten_ok()
- .try_collect()?;
-
- let (meet_id, transaction_id) = init_meeting(&handler, &signer, participants).await?;
-
- Ok(to_value(&Meeting {
- meet_id,
- transaction_id,
- }))
- };
-
- wasm_bindgen_futures::future_to_promise(async move {
- select! {
- meet = init_meeting.fuse() => meet,
- _ = TimeoutFuture::new(timeout_ms).fuse() => {
- Err(ApiError::CallTimeout("The initialization has been timed out".to_owned()).into())
- }
- }
- })
- }
-
- /// Sends participants' keys to the keys exchange server
- ///
- /// Arguments
- ///
- /// - meeting_id - The [`String`] that indicates ID of the meeting room
- /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
- pub fn send_keys(&self, meeting_id: String, timeout_ms: u32) -> Promise {
- let handler = self.handler();
- let signer = self.signer();
-
- let send_keys = async move {
- let meet_id = Uuid::from_str(&meeting_id).map_err(ApiError::from)?;
-
- let mut participants = view_meet_participants(&handler, meet_id)
- .await?
- .ok_or_else(|| {
- ApiError::InvalidSessionUuid(format!("Wrong Session ID: {meet_id}"))
- })?;
-
- info!("Get a meeting participants {participants:?}");
-
- while !participants.is_empty() {
- let infos =
- match public_keys(&handler, &signer, meet_id, participants.clone()).await {
- Ok(infos) => infos,
- Err(err) => {
- warn!("Failed to fetch a public keys, cause {err:?}");
- continue;
- }
- };
-
- // remove infos that is already processed
- for key in &infos {
- participants.remove(&key.account_id);
- }
-
- let messages = infos
- .into_iter()
- .map(|info| {
- let sk = signer.secret_key();
- let other_pk = info.public_key;
- let msg = encrypt(sk, other_pk, meet_id, &handler.secret)?;
- Ok::<ExchangeMessage, JsValue>(ExchangeMessage {
- account_id: info.account_id,
- message: Data {
- data: msg,
- moderator_pk: signer.public_key().to_owned(),
- },
- })
- })
- .try_collect()?;
-
- exchange(&handler, &signer, meet_id, messages).await?;
- }
-
- Ok(JsValue::from_str(&base64::encode(handler.secret)))
- };
-
- wasm_bindgen_futures::future_to_promise(async move {
- select! {
- aes_secret = send_keys.fuse() => aes_secret,
- _ = TimeoutFuture::new(timeout_ms).fuse() => {
- Err(ApiError::CallTimeout("The send keys operation has been timed out".to_owned()).into())
- }
- }
- })
- }
-
- /// Get participant's key from a server
- ///
- /// Arguments
- ///
- /// - meeting_id - The [`String`] that indicates ID of the meeting room
- /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
- pub fn get_key(&self, meeting_id: String, timeout_ms: u32) -> Promise {
- let handler = self.handler();
- let signer = self.signer();
-
- let get_key = async move {
- let meet_id = Uuid::from_str(&meeting_id).map_err(ApiError::from)?;
- let data = receive(&handler, &signer, meet_id).await?;
- let secret = decrypt(signer.secret_key(), data.moderator_pk, meet_id, data.data)?;
- Ok(JsValue::from_str(&base64::encode(secret)))
- };
-
- wasm_bindgen_futures::future_to_promise(async move {
- select! {
- key = get_key.fuse() => key,
- _ = TimeoutFuture::new(timeout_ms).fuse() => {
- Err(ApiError::CallTimeout("The getting key operation has been timed out".to_owned()).into())
- }
- }
- })
- }
- }
|