use std::{ collections::BTreeSet, sync::{Arc, Mutex}, }; use anyhow::Result; use chat_shared::{ChatSender, ChatTicket, EndpointId, TopicId}; use n0_future::StreamExt; use wasm_bindgen::{JsError, JsValue, prelude::wasm_bindgen}; use wasm_streams::ReadableStream; #[wasm_bindgen(start)] fn start() { console_error_panic_hook::set_once(); tracing_subscriber::fmt() .with_writer(tracing_subscriber_wasm::MakeConsoleWriter::default()) .without_time() .with_ansi(false) .init(); } #[wasm_bindgen] pub struct SheetNode(chat_shared::ChatNode); #[wasm_bindgen] impl SheetNode { pub async fn spawn() -> Result { let node = chat_shared::ChatNode::spawn(None) .await .map_err(to_js_error)?; Ok(Self(node)) } pub async fn create(&self, nickname: String) -> Result { self.join_inner(ChatTicket::new_random(), nickname).await } pub async fn join(&self, ticket: String, nickname: String) -> Result { let ticket = ChatTicket::deserialize(&ticket).map_err(to_js_error)?; self.join_inner(ticket, nickname).await } pub async fn shutdown(&self) { self.0.shutdown().await; } async fn join_inner( &self, mut ticket: ChatTicket, nickname: String, ) -> Result { let (sender, receiver) = self.0.join(&ticket, nickname).await.map_err(to_js_error)?; let neighbors = Arc::new(Mutex::new(BTreeSet::new())); let tracked_neighbors = neighbors.clone(); let receiver = receiver.map(move |event| { if let Ok(event) = &event { match event { chat_shared::Event::Joined { neighbors } => { tracked_neighbors .lock() .unwrap() .extend(neighbors.iter().copied()); } chat_shared::Event::NeighborUp { endpoint_id } => { tracked_neighbors.lock().unwrap().insert(*endpoint_id); } chat_shared::Event::NeighborDown { endpoint_id } => { tracked_neighbors.lock().unwrap().remove(endpoint_id); } _ => {} } } event .map_err(|error| JsValue::from_str(&error.to_string())) .map(|event| serde_wasm_bindgen::to_value(&event).unwrap()) }); let endpoint_id = self.0.endpoint_id(); ticket.bootstrap.insert(endpoint_id); Ok(SheetChannel { topic_id: ticket.topic_id, endpoint_id, bootstrap: ticket.bootstrap, neighbors, sender: ChannelSender(sender), receiver: ReadableStream::from_stream(receiver).into_raw(), }) } } type ChannelReceiver = wasm_streams::readable::sys::ReadableStream; #[wasm_bindgen] pub struct SheetChannel { topic_id: TopicId, endpoint_id: EndpointId, bootstrap: BTreeSet, neighbors: Arc>>, sender: ChannelSender, receiver: ChannelReceiver, } #[wasm_bindgen] impl SheetChannel { #[wasm_bindgen(getter)] pub fn sender(&self) -> ChannelSender { self.sender.clone() } #[wasm_bindgen(getter)] pub fn receiver(&self) -> ChannelReceiver { self.receiver.clone() } pub fn ticket(&self) -> String { let mut ticket = ChatTicket::new(self.topic_id); ticket.bootstrap.extend(self.bootstrap.iter().copied()); ticket .bootstrap .extend(self.neighbors.lock().unwrap().iter().copied()); ticket.bootstrap.insert(self.endpoint_id); ticket.serialize() } pub fn peer_count(&self) -> usize { self.neighbors.lock().unwrap().len() } } #[wasm_bindgen] #[derive(Clone)] pub struct ChannelSender(ChatSender); #[wasm_bindgen] impl ChannelSender { pub async fn broadcast(&self, payload: String) -> Result<(), JsError> { self.0.send(payload).await.map_err(to_js_error) } } fn to_js_error(error: impl Into) -> JsError { let error: anyhow::Error = error.into(); JsError::new(&error.to_string()) }