use log::debug;
use futures::{future, stream};
use futures::stream::Then as StreamThen;
use futures::sync::{mpsc, oneshot};
use futures::{Async, Future, Poll, Stream};
use multiaddr::{Protocol, Multiaddr};
use rw_stream_sink::RwStreamSink;
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};
use stdweb::web::TypedArray;
use stdweb::{self, Reference};
use libp2p_core::{Transport, transport::{ListenerEvent, TransportError}};
use tokio_io::{AsyncRead, AsyncWrite};
#[derive(Debug, Clone)]
pub struct BrowserWsConfig;
impl BrowserWsConfig {
#[inline]
pub fn new() -> BrowserWsConfig {
BrowserWsConfig
}
}
impl Transport for BrowserWsConfig {
type Output = BrowserWsConn;
type Error = IoError; type Listener = stream::Empty<ListenerEvent<Self::ListenerUpgrade>, IoError>;
type ListenerUpgrade = future::Empty<Self::Output, IoError>;
type Dial = Box<Future<Item = Self::Output, Error = IoError> + Send>;
fn listen_on(self, a: Multiaddr) -> Result<Self::Listener, TransportError<Self::Error>> {
Err(TransportError::MultiaddrNotSupported(a))
}
fn dial(self, original_addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
stdweb::initialize();
let inner_addr = match multiaddr_to_target(&original_addr) {
Ok(a) => a,
Err(_) => return Err(TransportError::MultiaddrNotSupported(original_addr)),
};
debug!("Dialing {}", original_addr);
let websocket = {
let val = js! {
try {
return new WebSocket(@{inner_addr});
} catch(e) {
return false;
}
};
match val.into_reference() {
Some(ws) => ws,
None => return Err(TransportError::Other(IoErrorKind::Other.into())), }
};
let (message_tx, message_rx) = mpsc::unbounded::<Result<Vec<u8>, IoError>>();
let message_tx = Arc::new(message_tx);
let mut message_rx = Some(message_rx);
let message_cb = {
let message_tx = message_tx.clone();
move |message_data: Reference| {
if let Some(buffer) = message_data.downcast::<TypedArray<u8>>() {
let _ = message_tx.unbounded_send(Ok(buffer.to_vec()));
} else {
let _ = message_tx.unbounded_send(Err(IoError::new(
IoErrorKind::InvalidData,
"received ws message of unknown type",
)));
}
}
};
let (open_tx, open_rx) = oneshot::channel::<Result<BrowserWsConn, IoError>>();
let open_tx = Arc::new(Mutex::new(Some(open_tx)));
let websocket_clone = websocket.clone();
let open_cb = {
let open_tx = open_tx.clone();
move || {
let tx = open_tx
.lock()
.unwrap()
.take()
.expect("the websocket can only open once");
let message_rx = message_rx.take().expect("the websocket can only open once");
let _ = tx.send(Ok(BrowserWsConn {
websocket: websocket_clone.clone(),
incoming_data: RwStreamSink::new(message_rx.then(|result| {
match result {
Ok(r) => r,
Err(_) => {
unreachable!("the message channel outlives the BrowserWsConn")
}
}
})),
}));
}
};
let close_cb = move || {
if let Some(tx) = open_tx.lock().unwrap().take() {
let _ = tx.send(Err(IoError::new(
IoErrorKind::ConnectionRefused,
"close event on the websocket",
)));
}
let _ = message_tx.unbounded_send(Err(IoError::new(
IoErrorKind::ConnectionRefused,
"close event on the websocket",
)));
};
js! {
var socket = @{websocket};
var open_cb = @{open_cb};
var message_cb = @{message_cb};
var close_cb = @{close_cb};
socket.addEventListener("open", function(event) {
open_cb();
});
socket.addEventListener("message", function(event) {
var reader = new FileReader();
reader.addEventListener("loadend", function() {
var typed = new Uint8Array(reader.result);
message_cb(typed);
});
reader.readAsArrayBuffer(event.data);
});
socket.addEventListener("close", function(event) {
close_cb();
});
};
Ok(Box::new(open_rx.then(|result| {
match result {
Ok(Ok(r)) => Ok(r),
Ok(Err(e)) => Err(e),
Err(_) => unreachable!("the sending side will only close when we drop the future"),
}
})) as Box<_>)
}
}
pub struct BrowserWsConn {
websocket: Reference,
incoming_data: RwStreamSink<
StreamThen<
mpsc::UnboundedReceiver<Result<Vec<u8>, IoError>>,
fn(Result<Result<Vec<u8>, IoError>, ()>) -> Result<Vec<u8>, IoError>,
Result<Vec<u8>, IoError>,
>,
>,
}
impl Drop for BrowserWsConn {
#[inline]
fn drop(&mut self) {
js! { @{&self.websocket}.close(); }
}
}
impl AsyncRead for BrowserWsConn {}
impl Read for BrowserWsConn {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize, IoError> {
self.incoming_data.read(buf)
}
}
impl AsyncWrite for BrowserWsConn {
#[inline]
fn shutdown(&mut self) -> Poll<(), IoError> {
Ok(Async::Ready(()))
}
}
impl Write for BrowserWsConn {
fn write(&mut self, buf: &[u8]) -> Result<usize, IoError> {
let typed_array = TypedArray::from(buf);
let returned = js! {
try {
@{&self.websocket}.send(@{typed_array.buffer()});
return true;
} catch(e) {
return false;
}
};
match returned {
stdweb::Value::Bool(true) => Ok(buf.len()),
stdweb::Value::Bool(false) => Err(IoError::new(
IoErrorKind::BrokenPipe,
"websocket has been closed by the remote",
)),
_ => unreachable!(),
}
}
#[inline]
fn flush(&mut self) -> Result<(), IoError> {
Ok(())
}
}
fn multiaddr_to_target(addr: &Multiaddr) -> Result<String, ()> {
let protocols: Vec<_> = addr.iter().collect();
if protocols.len() != 3 {
return Err(());
}
match (&protocols[0], &protocols[1], &protocols[2]) {
(&Protocol::Ip4(ref ip), &Protocol::Tcp(port), &Protocol::Ws) => {
if ip.is_unspecified() || port == 0 {
return Err(());
}
Ok(format!("ws://{}:{}/", ip, port))
}
(&Protocol::Ip6(ref ip), &Protocol::Tcp(port), &Protocol::Ws) => {
if ip.is_unspecified() || port == 0 {
return Err(());
}
Ok(format!("ws://[{}]:{}/", ip, port))
}
(&Protocol::Ip4(ref ip), &Protocol::Tcp(port), &Protocol::Wss) => {
if ip.is_unspecified() || port == 0 {
return Err(());
}
Ok(format!("wss://{}:{}/", ip, port))
}
(&Protocol::Ip6(ref ip), &Protocol::Tcp(port), &Protocol::Wss) => {
if ip.is_unspecified() || port == 0 {
return Err(());
}
Ok(format!("wss://[{}]:{}/", ip, port))
}
(&Protocol::Dns4(ref ns), &Protocol::Tcp(port), &Protocol::Ws) => {
Ok(format!("ws://{}:{}/", ns, port))
}
(&Protocol::Dns6(ref ns), &Protocol::Tcp(port), &Protocol::Ws) => {
Ok(format!("ws://{}:{}/", ns, port))
}
(&Protocol::Dns4(ref ns), &Protocol::Tcp(port), &Protocol::Wss) => {
Ok(format!("wss://{}:{}/", ns, port))
}
(&Protocol::Dns6(ref ns), &Protocol::Tcp(port), &Protocol::Wss) => {
Ok(format!("wss://{}:{}/", ns, port))
}
_ => Err(()),
}
}