wasm_ws/
message.rs

1// Copyright (c) 2019-2022 Naja Melan
2// Copyright (c) 2023-2024 Yuki Kishimoto
3// Distributed under the MIT software license
4
5use core::{fmt, str};
6
7use js_sys::{ArrayBuffer, Uint8Array};
8use wasm_bindgen::JsCast;
9use web_sys::{Blob, MessageEvent};
10
11use crate::WsErr;
12
13/// Represents a WebSocket Message, after converting from JavaScript type.
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum WsMessage {
16    /// The data of the message is a string.
17    Text(String),
18
19    /// The message contains binary data.
20    Binary(Vec<u8>),
21}
22
23impl WsMessage {
24    /// Get the length of the WebSocket message.
25    #[inline]
26    pub fn len(&self) -> usize {
27        match self {
28            Self::Text(string) => string.len(),
29            Self::Binary(data) => data.len(),
30        }
31    }
32
33    /// Returns true if the WebSocket message has no content.
34    /// For example, if the other side of the connection sent an empty string.
35    #[inline]
36    pub fn is_empty(&self) -> bool {
37        self.len() == 0
38    }
39
40    /// Consume the message and return it as binary data.
41    pub fn into_data(self) -> Vec<u8> {
42        match self {
43            Self::Text(string) => string.into_bytes(),
44            Self::Binary(data) => data,
45        }
46    }
47
48    /// Attempt to get a &str from the WebSocket message,
49    /// this will try to convert binary data to utf8.
50    pub fn to_text(&self) -> Result<&str, WsErr> {
51        match self {
52            Self::Text(string) => Ok(string),
53            Self::Binary(data) => Ok(str::from_utf8(data)?),
54        }
55    }
56}
57
58/// This will convert the JavaScript event into a WsMessage. Note that this
59/// will only work if the connection is set to use the binary type ArrayBuffer.
60/// On binary type Blob, this will panic.
61impl TryFrom<MessageEvent> for WsMessage {
62    type Error = WsErr;
63
64    fn try_from(evt: MessageEvent) -> Result<Self, Self::Error> {
65        match evt.data() {
66            d if d.is_instance_of::<ArrayBuffer>() => {
67                let buffy = Uint8Array::new(d.unchecked_ref());
68                let mut v = vec![0; buffy.length() as usize];
69
70                buffy.copy_to(&mut v); // FIXME: get rid of this copy
71
72                Ok(WsMessage::Binary(v))
73            }
74
75            // We don't allow invalid encodings. In principle if needed,
76            // we could add a variant to WsMessage with a CString or an OsString
77            // to allow the user to access this data. However until there is a usecase,
78            // I'm not inclined, amongst other things because the conversion from Js isn't very
79            // clear and it would require a bunch of testing for something that's a rather bad
80            // idea to begin with. If you need data that is not a valid string, use a binary
81            // message.
82            d if d.is_string() => match d.as_string() {
83                Some(text) => Ok(WsMessage::Text(text)),
84                None => Err(WsErr::InvalidEncoding),
85            },
86
87            // We have set the binary mode to array buffer (WsMeta::connect), so normally this shouldn't happen.
88            // That is as long as this is used within the context of the WsMeta constructor.
89            d if d.is_instance_of::<Blob>() => Err(WsErr::CantDecodeBlob),
90
91            // should never happen.
92            _ => Err(WsErr::UnknownDataType),
93        }
94    }
95}
96
97impl From<WsMessage> for Vec<u8> {
98    fn from(msg: WsMessage) -> Self {
99        match msg {
100            WsMessage::Text(string) => string.into(),
101            WsMessage::Binary(vec) => vec,
102        }
103    }
104}
105
106impl From<Vec<u8>> for WsMessage {
107    fn from(vec: Vec<u8>) -> Self {
108        WsMessage::Binary(vec)
109    }
110}
111
112impl From<String> for WsMessage {
113    fn from(s: String) -> Self {
114        WsMessage::Text(s)
115    }
116}
117
118impl AsRef<[u8]> for WsMessage {
119    fn as_ref(&self) -> &[u8] {
120        match self {
121            WsMessage::Text(string) => string.as_ref(),
122            WsMessage::Binary(vec) => vec.as_ref(),
123        }
124    }
125}
126
127impl fmt::Display for WsMessage {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        if let Ok(string) = self.to_text() {
130            write!(f, "{string}")
131        } else {
132            write!(f, "Binary Data<length={}>", self.len())
133        }
134    }
135}