use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use web_sys::{console, window, BroadcastChannel, MessageEvent};
use gloo::storage::{LocalStorage, Storage as _};
use gloo::worker::{Worker, WorkerBridge};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use js_sys::{Array, Promise, Uint8Array};
use crate::{
types::*,
error::Result,
, synapse::blockchain::serialization::{DateTimeWrapper, UuidWrapper}},;
use super::{
webrtc::WebRtcTransport,
websocket::WebSocketTransport,
storage::BrowserStorage,
worker::SynapseWorker,
crypto::WebCrypto,
};
#[wasm_bindgen]
pub struct BrowserSynapseNode {
entity_id: String,
webrtc_transport: Option<WebRtcTransport>,
websocket_transport: Option<WebSocketTransport>,
storage: BrowserStorage,
peers: Arc<Mutex<HashMap<String, BrowserPeer>>>,
connections: Arc<Mutex<HashMap<String, BrowserConnection>>>,
crypto: WebCrypto,
config: BrowserConfig,
worker: Option<WorkerBridge<SynapseWorker>>,
broadcast_channel: Option<BroadcastChannel>,
}
#[derive(Debug, Clone)]
pub struct BrowserPeer {
pub entity_id: String,
pub display_name: String,
pub capabilities: Vec<String>,
pub webrtc_supported: bool,
pub websocket_endpoints: Vec<String>,
pub ice_servers: Vec<IceServer>,
pub last_seen: f64, pub tab_id: Option<String>, }
#[derive(Debug)]
pub enum BrowserConnection {
WebRtc {
peer_id: String,
data_channel: web_sys::RtcDataChannel,
connection: web_sys::RtcPeerConnection,
},
WebSocket {
peer_id: String,
socket: web_sys::WebSocket,
relay_server: String,
},
BroadcastChannel {
peer_id: String,
channel: BroadcastChannel,
},
}
#[derive(Debug, Clone)]
pub struct IceServer {
pub urls: Vec<String>,
pub username: Option<String>,
pub credential: Option<String>,
}
#[derive(Debug, Clone)]
pub struct BrowserConfig {
pub ice_servers: Vec<IceServer>,
pub websocket_relays: Vec<String>,
pub enable_webrtc: bool,
pub enable_websocket: bool,
pub enable_broadcast_channel: bool,
pub storage_key_prefix: String,
pub max_connections: usize,
pub connection_timeout_ms: u32,
}
impl Default for BrowserConfig {
fn default() -> Self {
Self {
ice_servers: vec![
IceServer {
urls: vec!["stun:stun.l.google.com:19302".to_string()],
username: None,
credential: None,
},
IceServer {
urls: vec!["stun:stun1.l.google.com:19302".to_string()],
username: None,
credential: None,
},
],
websocket_relays: vec![
"wss://relay.synapse-network.org/ws".to_string(),
"wss://backup-relay.synapse-network.org/ws".to_string(),
],
enable_webrtc: true,
enable_websocket: true,
enable_broadcast_channel: true,
storage_key_prefix: "synapse_".to_string(),
max_connections: 50,
connection_timeout_ms: 30000,
}
}
}
#[wasm_bindgen]
impl BrowserSynapseNode {
#[wasm_bindgen(constructor)]
pub fn new(entity_id: String) -> BrowserSynapseNode {
console_error_panic_hook::set_once();
let config = BrowserConfig::default();
let storage = BrowserStorage::new(&config.storage_key_prefix);
let crypto = WebCrypto::new();
console::log_1(&"Creating Synapse node in browser...".into());
Self {
entity_id,
webrtc_transport: None,
websocket_transport: None,
storage,
peers: Arc::new(Mutex::new(HashMap::new())),
connections: Arc::new(Mutex::new(HashMap::new())),
crypto,
config,
worker: None,
broadcast_channel: None,
}
}
#[wasm_bindgen]
pub async fn initialize(&mut self) -> Result<(), JsValue> {
console::log_1(&"Initializing Synapse browser node...".into());
if self.config.enable_webrtc {
match WebRtcTransport::new(self.entity_id.clone(), self.config.ice_servers.clone()).await {
Ok(transport) => {
console::log_1(&"WebRTC transport initialized".into());
self.webrtc_transport = Some(transport);
}
Err(e) => {
console::warn_1(&format!("Failed to initialize WebRTC: {:?}", e).into());
}
}
}
if self.config.enable_websocket {
match WebSocketTransport::new(self.entity_id.clone(), self.config.websocket_relays.clone()).await {
Ok(transport) => {
console::log_1(&"WebSocket transport initialized".into());
self.websocket_transport = Some(transport);
}
Err(e) => {
console::warn_1(&format!("Failed to initialize WebSocket: {:?}", e).into());
}
}
}
if self.config.enable_broadcast_channel {
match BroadcastChannel::new("synapse") {
Ok(channel) => {
self.setup_broadcast_channel(&channel)?;
self.broadcast_channel = Some(channel);
console::log_1(&"Broadcast channel initialized".into());
}
Err(e) => {
console::warn_1(&format!("Failed to initialize broadcast channel: {:?}", e).into());
}
}
}
self.start_worker().await?;
self.load_persisted_data().await?;
console::log_1(&"Synapse browser node fully initialized".into());
Ok(())
}
#[wasm_bindgen]
pub async fn send_message(&self, target: String, message: String) -> Result<String, JsValue> {
console::log_2(&"Sending message to".into(), &target.into());
if let Some(peer) = self.find_peer(&target).await {
if peer.webrtc_supported {
if let Some(webrtc) = &self.webrtc_transport {
match webrtc.send_message(&target, &message).await {
Ok(result) => return Ok(result),
Err(e) => console::warn_1(&format!("WebRTC send failed: {:?}", e).into()),
}
}
}
if !peer.websocket_endpoints.is_empty() {
if let Some(websocket) = &self.websocket_transport {
match websocket.send_message(&target, &message).await {
Ok(result) => return Ok(result),
Err(e) => console::warn_1(&format!("WebSocket send failed: {:?}", e).into()),
}
}
}
if peer.tab_id.is_some() {
if let Some(broadcast) = &self.broadcast_channel {
match self.send_via_broadcast_channel(broadcast, &target, &message).await {
Ok(result) => return Ok(result),
Err(e) => console::warn_1(&format!("Broadcast channel send failed: {:?}", e).into()),
}
}
}
}
Err(JsValue::from_str(&format!("Failed to send message to {}", target)))
}
#[wasm_bindgen]
pub async fn discover_peers(&self) -> Result<Array, JsValue> {
console::log_1(&"Discovering peers...".into());
let mut discovered = Vec::new();
if let Some(webrtc) = &self.webrtc_transport {
if let Ok(peers) = webrtc.discover_peers().await {
discovered.extend(peers);
}
}
if let Some(websocket) = &self.websocket_transport {
if let Ok(peers) = websocket.discover_peers().await {
discovered.extend(peers);
}
}
if let Some(broadcast) = &self.broadcast_channel {
if let Ok(peers) = self.discover_via_broadcast_channel(broadcast).await {
discovered.extend(peers);
}
}
let js_array = Array::new();
for peer in discovered {
let peer_obj = js_sys::Object::new();
js_sys::Reflect::set(&peer_obj, &"entity_id".into(), &peer.entity_id.into())?;
js_sys::Reflect::set(&peer_obj, &"display_name".into(), &peer.display_name.into())?;
js_array.push(&peer_obj);
}
Ok(js_array)
}
#[wasm_bindgen]
pub fn get_status(&self) -> js_sys::Object {
let status = js_sys::Object::new();
let connections = self.connections.lock().unwrap();
js_sys::Reflect::set(&status, &"active_connections".into(), &(connections.len() as u32).into()).unwrap();
let webrtc_available = self.webrtc_transport.is_some();
js_sys::Reflect::set(&status, &"webrtc_available".into(), &webrtc_available.into()).unwrap();
let websocket_available = self.websocket_transport.is_some();
js_sys::Reflect::set(&status, &"websocket_available".into(), &websocket_available.into()).unwrap();
let broadcast_available = self.broadcast_channel.is_some();
js_sys::Reflect::set(&status, &"broadcast_available".into(), &broadcast_available.into()).unwrap();
status
}
#[wasm_bindgen]
pub fn set_message_handler(&self, handler: &js_sys::Function) {
console::log_1(&"Message handler set".into());
}
async fn find_peer(&self, entity_id: &str) -> Option<BrowserPeer> {
let peers = self.peers.lock().unwrap();
peers.get(entity_id).cloned()
}
fn setup_broadcast_channel(&self, channel: &BroadcastChannel) -> Result<(), JsValue> {
let entity_id = self.entity_id.clone();
let peers = Arc::clone(&self.peers);
let onmessage_callback = Closure::wrap(Box::new(move |event: MessageEvent| {
console::log_2(&"Received broadcast message from".into(), &event.data());
}) as Box<dyn FnMut(_)>);
channel.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();
Ok(())
}
async fn send_via_broadcast_channel(
&self,
channel: &BroadcastChannel,
target: &str,
message: &str,
) -> Result<String, JsValue> {
let msg = js_sys::Object::new();
js_sys::Reflect::set(&msg, &"type".into(), &"synapse_message".into())?;
js_sys::Reflect::set(&msg, &"from".into(), &self.entity_id.clone().into())?;
js_sys::Reflect::set(&msg, &"to".into(), &target.into())?;
js_sys::Reflect::set(&msg, &"message".into(), &message.into())?;
js_sys::Reflect::set(&msg, &"timestamp".into(), &js_sys::Date::now().into())?;
channel.post_message(&msg)?;
Ok(format!("broadcast://{}@{}", target, self.entity_id))
}
async fn discover_via_broadcast_channel(&self, channel: &BroadcastChannel) -> Result<Vec<BrowserPeer>, JsValue> {
let discovery_msg = js_sys::Object::new();
js_sys::Reflect::set(&discovery_msg, &"type".into(), &"synapse_discovery".into())?;
js_sys::Reflect::set(&discovery_msg, &"from".into(), &self.entity_id.clone().into())?;
js_sys::Reflect::set(&discovery_msg, &"timestamp".into(), &js_sys::Date::now().into())?;
channel.post_message(&discovery_msg)?;
Ok(Vec::new())
}
async fn start_worker(&mut self) -> Result<(), JsValue> {
console::log_1(&"Starting Synapse worker...".into());
Ok(())
}
async fn load_persisted_data(&self) -> Result<(), JsValue> {
if let Ok(peers_data) = LocalStorage::get::<String>(&format!("{}peers", self.config.storage_key_prefix)) {
console::log_1(&"Loaded persisted peer data".into());
}
Ok(())
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn warn(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn error(s: &str);
}
#[wasm_bindgen]
pub fn init_panic_hook() {
console_error_panic_hook::set_once();
}
#[wasm_bindgen]
pub fn get_memory_usage() -> js_sys::Object {
let usage = js_sys::Object::new();
if let Some(memory) = wasm_bindgen::memory() {
let buffer = memory.buffer();
let size = buffer.byte_length();
js_sys::Reflect::set(&usage, &"total_bytes".into(), &size.into()).unwrap();
}
usage
}
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;