use yew::worker::*;
use crate::coapwsmessage::{CoAPWSMessageR, CoAPWSMessageW};
use coap_message::{MinimalWritableMessage, ReadableMessage};
use coap_handler_implementations::option_processing::OptionsExt;
pub enum Input {
Initialize { uri: String },
Message(CoAPWSMessageW),
}
#[derive(Debug)]
pub enum Output {
Connected,
Message(CoAPWSMessageR<Box<[u8]>>),
SignalingInfo(CoAPWSMessageR<Box<[u8]>>),
Error(OutputError)
}
#[derive(Debug)]
#[non_exhaustive]
pub enum OutputError {
ConnectError(String),
ParseError(&'static str),
TextReceived(String),
SignalingError(CoAPWSMessageR<Box<[u8]>>),
Closed,
SocketError,
}
impl From<OutputError> for Output {
fn from(e: OutputError) -> Output {
Output::Error(e)
}
}
pub struct Connection {
tasks: std::collections::HashMap<HandlerId, yew::services::websocket::WebSocketTask>,
link: AgentLink<Self>,
}
impl Agent for Connection {
type Reach = Context<Self>;
type Message = (HandlerId, WSMsg);
type Input = Input;
type Output = Output;
fn create(
link: AgentLink<Self>,
) -> Self {
let tasks = Default::default();
Connection {
tasks,
link,
}
}
fn disconnected(&mut self, id: HandlerId) {
drop(self.tasks.remove(&id));
}
fn update(&mut self, (handler, msg): (HandlerId, WSMsg)) {
let task = self.tasks.get_mut(&handler);
let task = match task {
Some(t) => t,
None => return,
};
match msg {
WSMsg::Notif(yew::services::websocket::WebSocketStatus::Opened) => {
let mut empty_csm = CoAPWSMessageW::new(&[]);
empty_csm.set_code(coap_numbers::code::CSM);
task.send_binary(Ok(empty_csm.serialize()));
self.link.respond(handler, Output::Connected);
}
WSMsg::Binary(data) => {
let msg = CoAPWSMessageR::new(data.into_boxed_slice());
let msg = match msg {
Err(e) => {
self.link.respond(handler, OutputError::ParseError(e).into());
self.tasks.remove(&handler);
return;
}
Ok(m) => m,
};
if coap_numbers::code::classify(msg.code()) == coap_numbers::code::Range::Signaling {
match msg.code() {
coap_numbers::code::CSM | coap_numbers::code::RELEASE => {
let opts = msg.options().ignore_elective_others();
if opts.is_err() {
self.link.respond(handler, OutputError::SignalingError(msg).into());
self.tasks.remove(&handler);
return;
}
},
coap_numbers::code::PING => {
let mut pong = CoAPWSMessageW::new(msg.token());
pong.set_code(coap_numbers::code::PONG);
let opts = msg.options().ignore_elective_others();
if opts.is_err() {
self.link.respond(handler, OutputError::SignalingError(msg).into());
self.tasks.remove(&handler);
return;
}
task.send_binary(Ok(pong.serialize()));
}
_ => {
self.link.respond(handler, OutputError::SignalingError(msg).into());
self.tasks.remove(&handler);
return;
},
}
self.link.respond(handler, Output::SignalingInfo(msg));
return;
}
self.link.respond(handler, Output::Message(msg));
}
WSMsg::Text(data) => {
self.link.respond(handler, OutputError::TextReceived(data).into());
self.tasks.remove(&handler);
}
WSMsg::Notif(yew::services::websocket::WebSocketStatus::Closed) => {
self.link.respond(handler, OutputError::Closed.into());
self.tasks.remove(&handler);
}
WSMsg::Notif(yew::services::websocket::WebSocketStatus::Error) | WSMsg::YewBinaryError(_) | WSMsg::YewTextError(_) => {
self.link.respond(handler, OutputError::SocketError.into());
self.tasks.remove(&handler);
}
}
}
fn handle_input(&mut self, input: Input, handler: HandlerId) {
match input {
Input::Initialize { uri } => {
let task = yew::services::websocket::WebSocketService::connect(
&uri,
self.link.callback(move |m: WSMsg| (handler, m)),
self.link.callback(move |n| (handler, WSMsg::Notif(n))),
);
let task = match task {
Err(e) => {
self.link.respond(handler, OutputError::ConnectError(e.to_string()).into());
return;
}
Ok(t) => t
};
let old = self.tasks.insert(handler, task);
debug_assert!(old.is_none(), "Double initialization message");
}
Input::Message(msg) => {
let task = self.tasks.get_mut(&handler);
let task = match task {
None => return,
Some(x) => x,
};
task.send_binary(Ok(msg.serialize()));
}
}
}
}
#[derive(Debug)]
#[doc(hidden)]
pub enum WSMsg {
Notif(yew::services::websocket::WebSocketStatus),
Binary(Vec<u8>),
Text(String),
YewBinaryError(anyhow::Error),
YewTextError(anyhow::Error),
}
impl From<yew::format::Binary> for WSMsg {
fn from(input: yew::format::Binary) -> Self {
input.map(|b| WSMsg::Binary(b)).unwrap_or_else(WSMsg::YewBinaryError)
}
}
impl From<yew::format::Text> for WSMsg {
fn from(input: yew::format::Text) -> Self {
input.map(|s| WSMsg::Text(s)).unwrap_or_else(|e| WSMsg::YewTextError(e))
}
}