use std::cell::Cell;
use std::rc::Rc;
use bytes::Bytes;
use futures_channel::mpsc::{UnboundedReceiver, unbounded};
use futures_util::StreamExt;
use js_sys::Uint8Array;
use serde_json::value::RawValue;
use wasm_bindgen::JsCast;
use wasm_bindgen::closure::Closure;
use web_sys::{Event, MessageEvent, MessagePort};
use super::{Transport, TransportError, TransportReader, TransportWriter, envelope};
use crate::Server;
use crate::raw::RawMessage;
const DEFAULT_MAX_MESSAGE_SIZE: usize = 16 * 1024 * 1024;
pub fn worker_channel<S>(server: Server<S>, port: MessagePort) -> WorkerChannelBuilder<S>
where
S: Send + Sync + 'static,
{
WorkerChannelBuilder { server, port }
}
pub struct WorkerChannelBuilder<S> {
server: Server<S>,
port: MessagePort,
}
impl<S> WorkerChannelBuilder<S>
where
S: Send + Sync + 'static,
{
pub async fn serve(self) -> crate::Result<crate::Outcome> {
self.server
.serve(WorkerChannelTransport::new(self.port)?)
.await
}
}
pub struct WorkerChannelTransport {
reader: WorkerChannelReader,
writer: WorkerChannelWriter,
}
pub struct WorkerChannelReader {
port: MessagePort,
incoming: UnboundedReceiver<Result<RawMessage, TransportError>>,
closed: Rc<Cell<bool>>,
_on_message: Closure<dyn FnMut(MessageEvent)>,
_on_message_error: Closure<dyn FnMut(Event)>,
on_close: Closure<dyn FnMut(Event)>,
}
pub struct WorkerChannelWriter {
port: MessagePort,
closed: Rc<Cell<bool>>,
}
impl WorkerChannelTransport {
pub fn new(port: MessagePort) -> Result<Self, TransportError> {
let (incoming_tx, incoming) = unbounded();
let closed = Rc::new(Cell::new(false));
let message_tx = incoming_tx.clone();
let closed_on_message = Rc::clone(&closed);
let on_message = Closure::new(move |event: MessageEvent| {
if closed_on_message.get() {
return;
}
let result = parse_event_data(event.data());
if message_tx.unbounded_send(result).is_err() {
closed_on_message.set(true);
}
});
let message_error_tx = incoming_tx.clone();
let closed_on_error = Rc::clone(&closed);
let on_message_error = Closure::new(move |_event: Event| {
closed_on_error.set(true);
let _ = message_error_tx.unbounded_send(Err(TransportError::Malformed(
"MessagePort could not deserialize an incoming message".to_string(),
)));
});
let closed_on_close = Rc::clone(&closed);
let on_close = Closure::new(move |_event: Event| {
closed_on_close.set(true);
let _ = incoming_tx.unbounded_send(Err(TransportError::Closed));
});
port.add_event_listener_with_callback("close", on_close.as_ref().unchecked_ref())
.map_err(|error| {
TransportError::Malformed(format!(
"failed to register MessagePort close listener: {error:?}"
))
})?;
port.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
port.set_onmessageerror(Some(on_message_error.as_ref().unchecked_ref()));
port.start();
Ok(Self {
reader: WorkerChannelReader {
port: port.clone(),
incoming,
closed: Rc::clone(&closed),
_on_message: on_message,
_on_message_error: on_message_error,
on_close,
},
writer: WorkerChannelWriter { port, closed },
})
}
}
impl Transport for WorkerChannelTransport {
type Reader = WorkerChannelReader;
type Writer = WorkerChannelWriter;
fn split(self) -> (Self::Reader, Self::Writer) {
(self.reader, self.writer)
}
}
impl TransportReader for WorkerChannelReader {
async fn recv(&mut self) -> Result<RawMessage, TransportError> {
self.incoming
.next()
.await
.unwrap_or(Err(TransportError::Closed))
}
}
impl Drop for WorkerChannelReader {
fn drop(&mut self) {
self.closed.set(true);
self.port.set_onmessage(None);
self.port.set_onmessageerror(None);
let _ = self
.port
.remove_event_listener_with_callback("close", self.on_close.as_ref().unchecked_ref());
}
}
impl TransportWriter for WorkerChannelWriter {
async fn send(&mut self, msg: RawMessage) -> Result<(), TransportError> {
if self.closed.get() {
return Err(TransportError::Closed);
}
let body = envelope::serialize(&msg)?;
enforce_limit(body.len())?;
let text = String::from_utf8(body)
.map_err(|error| TransportError::Malformed(error.to_string()))?;
self.port
.post_message(&text.into())
.map_err(|_| TransportError::Closed)
}
async fn shutdown(self) -> Result<(), TransportError> {
self.close();
Ok(())
}
}
impl WorkerChannelWriter {
fn close(&self) {
self.closed.set(true);
self.port.close();
}
}
impl Drop for WorkerChannelWriter {
fn drop(&mut self) {
self.close();
}
}
fn parse_event_data(data: wasm_bindgen::JsValue) -> Result<RawMessage, TransportError> {
let body = if let Some(text) = data.as_string() {
text.into_bytes()
} else if let Ok(array) = data.dyn_into::<Uint8Array>() {
array.to_vec()
} else {
return Err(TransportError::Malformed(
"MessagePort data must be a string or Uint8Array".to_string(),
));
};
enforce_limit(body.len())?;
std::str::from_utf8(&body).map_err(|error| {
TransportError::Malformed(format!("MessagePort data is not UTF-8: {error}"))
})?;
serde_json::from_slice::<&RawValue>(&body)
.map_err(|error| TransportError::Malformed(format!("invalid JSON envelope: {error}")))?;
Ok(envelope::parse(Bytes::from(body)))
}
fn enforce_limit(length: usize) -> Result<(), TransportError> {
if length > DEFAULT_MAX_MESSAGE_SIZE {
Err(TransportError::OversizedMessage {
length,
limit: DEFAULT_MAX_MESSAGE_SIZE,
})
} else {
Ok(())
}
}