1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright (c) 2019-2022 Naja Melan
// Copyright (c) 2023-2024 Yuki Kishimoto
// Distributed under the MIT software license

use js_sys::{ArrayBuffer, Uint8Array};
use wasm_bindgen::JsCast;
use web_sys::{Blob, MessageEvent};

use crate::WsErr;

/// Represents a WebSocket Message, after converting from JavaScript type.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WsMessage {
    /// The data of the message is a string.
    Text(String),

    /// The message contains binary data.
    Binary(Vec<u8>),
}

impl WsMessage {
    /// Get the length of the WebSocket message.
    #[inline]
    pub fn len(&self) -> usize {
        match self {
            Self::Text(string) => string.len(),
            Self::Binary(data) => data.len(),
        }
    }

    /// Returns true if the WebSocket message has no content.
    /// For example, if the other side of the connection sent an empty string.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Consume the message and return it as binary data.
    pub fn into_data(self) -> Vec<u8> {
        match self {
            Self::Text(string) => string.into_bytes(),
            Self::Binary(data) => data,
        }
    }
}

/// This will convert the JavaScript event into a WsMessage. Note that this
/// will only work if the connection is set to use the binary type ArrayBuffer.
/// On binary type Blob, this will panic.
impl TryFrom<MessageEvent> for WsMessage {
    type Error = WsErr;

    fn try_from(evt: MessageEvent) -> Result<Self, Self::Error> {
        match evt.data() {
            d if d.is_instance_of::<ArrayBuffer>() => {
                let buffy = Uint8Array::new(d.unchecked_ref());
                let mut v = vec![0; buffy.length() as usize];

                buffy.copy_to(&mut v); // FIXME: get rid of this copy

                Ok(WsMessage::Binary(v))
            }

            // We don't allow invalid encodings. In principle if needed,
            // we could add a variant to WsMessage with a CString or an OsString
            // to allow the user to access this data. However until there is a usecase,
            // I'm not inclined, amongst other things because the conversion from Js isn't very
            // clear and it would require a bunch of testing for something that's a rather bad
            // idea to begin with. If you need data that is not a valid string, use a binary
            // message.
            d if d.is_string() => match d.as_string() {
                Some(text) => Ok(WsMessage::Text(text)),
                None => Err(WsErr::InvalidEncoding),
            },

            // We have set the binary mode to array buffer (WsMeta::connect), so normally this shouldn't happen.
            // That is as long as this is used within the context of the WsMeta constructor.
            d if d.is_instance_of::<Blob>() => Err(WsErr::CantDecodeBlob),

            // should never happen.
            _ => Err(WsErr::UnknownDataType),
        }
    }
}

impl From<WsMessage> for Vec<u8> {
    fn from(msg: WsMessage) -> Self {
        match msg {
            WsMessage::Text(string) => string.into(),
            WsMessage::Binary(vec) => vec,
        }
    }
}

impl From<Vec<u8>> for WsMessage {
    fn from(vec: Vec<u8>) -> Self {
        WsMessage::Binary(vec)
    }
}

impl From<String> for WsMessage {
    fn from(s: String) -> Self {
        WsMessage::Text(s)
    }
}

impl AsRef<[u8]> for WsMessage {
    fn as_ref(&self) -> &[u8] {
        match self {
            WsMessage::Text(string) => string.as_ref(),
            WsMessage::Binary(vec) => vec.as_ref(),
        }
    }
}