stdweb/webapi/
web_socket.rs

1use webcore::value::{Value, Reference, ConversionError};
2use webcore::try_from::{TryFrom, TryInto};
3use webcore::unsafe_typed_array::UnsafeTypedArray;
4use webapi::event_target::{IEventTarget, EventTarget};
5use webapi::blob::Blob;
6use webapi::array_buffer::ArrayBuffer;
7use webapi::dom_exception::{InvalidAccessError, SecurityError, SyntaxError};
8use private::TODO;
9
10/// Wrapper type around a CloseEvent code, indicating why the WebSocket was closed
11///
12/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)
13#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct SocketCloseCode(pub u16);
15
16// Close codes are defined here:
17// https://tools.ietf.org/html/rfc6455#section-7.4
18newtype_enum!(SocketCloseCode {
19    /// Normal closure; the connection successfully completed whatever purpose for which it was
20    /// created.
21    NORMAL_CLOSURE = 1000,
22    /// The endpoint is going away, either because of a server failure or because the browser is
23    /// navigating away from the page that opened the connection.
24    GOING_AWAY = 1001,
25    /// The endpoint is terminating the connection due to a protocol error.
26    PROTOCOL_ERROR = 1002,
27    /// The connection is being terminated because the endpoint received data of a type it cannot
28    /// accept (for example, a text-only endpoint received binary data).
29    UNSUPPORTED_DATA = 1003,
30    /// Reserved. Indicates that no status code was provided even though one was expected.
31    NO_STATUS_RECEIVED = 1005,
32    /// Reserved. Used to indicate that a connection was closed abnormally (that is, with no close
33    /// frame being sent) when a status code is expected.
34    ABNORMAL_CLOSURE = 1006,
35    /// The endpoint is terminating the connection because a message was received that contained
36    /// inconsistent data (e.g., non-UTF-8 data within a text message).
37    INVALID_FRAME_PAYLOAD_DATA = 1007,
38    /// The endpoint is terminating the connection because it received a message that violates its
39    /// policy. This is a generic status code, used when codes 1003 and 1009 are not suitable.
40    POLICY_VIOLATION = 1008,
41    /// The endpoint is terminating the connection because a data frame was received that is too
42    /// large.
43    MESSAGE_TOO_BIG = 1009,
44    /// The client is terminating the connection because it expected the server to negotiate one or
45    /// more extensions, but the server didn't.
46    MISSING_EXTENSION = 1010,
47    /// The server is terminating the connection because it encountered an unexpected condition
48    /// that prevented it from fulfilling the request.
49    INTERNAL_ERROR = 1011,
50    /// The server is terminating the connection because it is restarting.
51    SERVICE_RESTART = 1012,
52    /// The server is terminating the connection due to a temporary condition, e.g. it is
53    /// overloaded and is casting off some of its clients.
54    TRY_AGAIN_LATER = 1013,
55    /// The server was acting as a gateway or proxy and received an invalid response from the
56    /// upstream server. This is similar to 502 HTTP Status Code.
57    BAD_GATEWAY = 1014,
58    /// Reserved. Indicates that the connection was closed due to a failure to perform a TLS
59    /// handshake (e.g., the server certificate can't be verified).
60    TLS_HANDSHAKE = 1015,
61});
62
63/// The WebSocket object provides the API for creating and managing a WebSocket connection to a
64/// server, as well as for sending and receiving data on the connection.
65///
66/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
67// https://html.spec.whatwg.org/#websocket
68#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
69#[reference(instance_of = "WebSocket")]
70#[reference(subclass_of(EventTarget))]
71pub struct WebSocket( Reference );
72
73impl IEventTarget for WebSocket {}
74
75/// The type of binary data being transmitted by the WebSocket connection.
76///
77/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Attributes)
78// https://html.spec.whatwg.org/#dom-websocket-binarytype
79#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub enum SocketBinaryType {
81    /// A Blob object represents a file-like object of immutable, raw data.
82    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
83    Blob,
84    /// The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer.
85    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)
86    ArrayBuffer
87}
88
89impl SocketBinaryType {
90    fn to_str(self) -> &'static str {
91        match self {
92            SocketBinaryType::Blob => "blob",
93            SocketBinaryType::ArrayBuffer => "arraybuffer",
94        }
95    }
96    fn from_str(s: &str) -> Self {
97        match s {
98            "blob" => SocketBinaryType::Blob,
99            "arraybuffer" => SocketBinaryType::ArrayBuffer,
100            other => panic!("Invalid binary type: {:?}", other)
101        }
102    }
103}
104
105/// A number indicating the state of the `WebSocket`.
106///
107/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants)
108// https://html.spec.whatwg.org/#dom-websocket-readystate
109#[allow(missing_docs)]
110#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub enum SocketReadyState {
112    Connecting = 0,
113    Open = 1,
114    Closing = 2,
115    Closed = 3
116}
117
118impl TryFrom<Value> for SocketReadyState {
119    type Error = ConversionError;
120
121    /// Performs the conversion.
122    fn try_from(v: Value) -> Result<SocketReadyState, ConversionError> {
123        match v.try_into()? {
124            0 => Ok(SocketReadyState::Connecting),
125            1 => Ok(SocketReadyState::Open),
126            2 => Ok(SocketReadyState::Closing),
127            3 => Ok(SocketReadyState::Closed),
128            other => Err(ConversionError::Custom(format!("Unknown ready_state: {}", other)))
129        }
130    }
131}
132
133impl WebSocket {
134    /// Returns a newly constructed `WebSocket`.
135    ///
136    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
137    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket
138    pub fn new(url: &str) -> Result<WebSocket, CreationError> {
139        js_try!(
140            return new WebSocket(@{url});
141        ).unwrap()
142    }
143
144    /// Returns a newly constructed `WebSocket`.
145    ///
146    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
147    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket
148    pub fn new_with_protocols(url: &str, protocols: &[&str]) -> Result<WebSocket, CreationError> {
149        js_try!(
150            return new WebSocket(@{url}, @{protocols});
151        ).unwrap()
152    }
153
154    /// Returns the binary type of the web socket. Only affects received messages.
155    /// The default binary type is `Blob`.
156    ///
157    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
158    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-binarytype
159    pub fn binary_type(&self) -> SocketBinaryType {
160        let binary_type: String = js!( return @{self}.binaryType; ).try_into().unwrap();
161        SocketBinaryType::from_str(&binary_type)
162    }
163
164    /// Sets the binary type of the web socket. Only affects received messages.
165    /// The default binary type is `Blob`.
166    ///
167    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
168    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-binarytype
169    pub fn set_binary_type(&self, binary_type: SocketBinaryType) {
170        js!( @(no_return) @{self}.binaryType = @{binary_type.to_str()}; );
171    }
172
173    /// Returns the number of bytes of data that have been queued using calls to send()
174    /// but not yet transmitted to the network. This value resets to zero once all queued
175    /// data has been sent. This value does not reset to zero when the connection is closed;
176    /// if you keep calling send(), this will continue to climb.
177    ///
178    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
179    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-bufferedamount
180    pub fn buffered_amount(&self) -> u64 {
181        js!( return @{self}.bufferedAmount; ).try_into().unwrap()
182    }
183
184    /// Returns the extensions selected by the server. This is currently only the empty
185    /// string or a list of extensions as negotiated by the connection.
186    ///
187    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
188    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-extensions
189    pub fn extensions(&self) -> String {
190        js!( return @{self}.extensions; ).try_into().unwrap()
191    }
192
193    /// Returns a string indicating the name of the sub-protocol the server selected;
194    /// this will be one of the strings specified in the protocols parameter when
195    /// creating the WebSocket object.
196    ///
197    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
198    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-protocol
199    pub fn protocol(&self) -> String {
200        js!( return @{self}.protocol; ).try_into().unwrap()
201    }
202
203    /// Returns the current state of the connection.
204    ///
205    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
206    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-readystate
207    pub fn ready_state(&self) -> SocketReadyState {
208        js!( return @{self}.readyState; ).try_into().unwrap()
209    }
210
211    /// Returns the URL as resolved by the constructor. This is always an absolute URL.
212    ///
213    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
214    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-url
215    pub fn url(&self) -> String {
216        js!( return @{self}.url; ).try_into().unwrap()
217    }
218
219    /// Closes the WebSocket connection or connection attempt, if any. If the connection
220    /// is already CLOSED, this method does nothing.
221    ///
222    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
223    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-close
224    pub fn close(&self) {
225        js!( @(no_return) @{self}.close(); );
226    }
227
228    /// Closes the WebSocket connection or connection attempt, if any. If the connection
229    /// is already CLOSED, this method does nothing.
230    ///
231    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
232    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-close
233    pub fn close_with_status(&self, code: SocketCloseCode, reason: &str) -> Result<(), CloseError> {
234        js_try!( @(no_return)
235            @{self}.close(@{code.0}, @{reason});
236        ).unwrap()
237    }
238
239    /// Enqueues the specified data to be transmitted to the server over the WebSocket
240    /// connection, increasing the value of bufferedAmount by the number of bytes needed
241    /// to contain the data. If the data can't be sent (for example, because it needs to
242    /// be buffered but the buffer is full), the socket is closed automatically.
243    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
244    pub fn send_text(&self, text: &str) -> Result< (), TODO > {
245        js!( @(no_return) @{self}.send(@{text}); );
246        Ok(())
247    }
248
249    /// Enqueues the specified data to be transmitted to the server over the WebSocket
250    /// connection, increasing the value of bufferedAmount by the number of bytes needed
251    /// to contain the data. If the data can't be sent (for example, because it needs to
252    /// be buffered but the buffer is full), the socket is closed automatically.
253    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
254    pub fn send_blob(&self, blob: &Blob) -> Result< (), TODO > {
255        js!( @(no_return) @{self}.send(@{blob}); );
256        Ok(())
257    }
258
259    /// Enqueues the specified data to be transmitted to the server over the WebSocket
260    /// connection, increasing the value of bufferedAmount by the number of bytes needed
261    /// to contain the data. If the data can't be sent (for example, because it needs to
262    /// be buffered but the buffer is full), the socket is closed automatically.
263    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
264    pub fn send_array_buffer(&self, array_buffer: &ArrayBuffer) -> Result< (), TODO > {
265        js!( @(no_return) @{self}.send(@{array_buffer}); );
266        Ok(())
267    }
268
269    /// Enqueues the specified data to be transmitted to the server over the WebSocket
270    /// connection, increasing the value of bufferedAmount by the number of bytes needed
271    /// to contain the data. If the data can't be sent (for example, because it needs to
272    /// be buffered but the buffer is full), the socket is closed automatically.
273    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
274    pub fn send_bytes(&self, bytes: &[u8]) -> Result< (), TODO > {
275        js!( @(no_return) @{self}.send(@{ UnsafeTypedArray(bytes) }); );
276        Ok(())
277    }
278}
279
280/// Errors thrown by `WebSocket::new`.
281error_enum_boilerplate! {
282    CreationError,
283    SecurityError, SyntaxError
284}
285
286/// Errors thrown by `WebSocket::close_with_status`.
287error_enum_boilerplate! {
288    CloseError,
289    InvalidAccessError, SyntaxError
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    #[test]
297    fn test_close_codes() {
298        assert_eq!(&format!("{:?}", SocketCloseCode::NORMAL_CLOSURE), "SocketCloseCode::NORMAL_CLOSURE");
299        assert_eq!(&format!("{:?}", SocketCloseCode::GOING_AWAY), "SocketCloseCode::GOING_AWAY");
300        assert_eq!(&format!("{:?}", SocketCloseCode(1000)), "SocketCloseCode::NORMAL_CLOSURE");
301        assert_eq!(&format!("{:?}", SocketCloseCode(3001)), "SocketCloseCode(3001)");
302    }
303}
304
305#[cfg(all(test, feature = "web_test"))]
306mod web_tests {
307    use super::*;
308
309    #[test]
310    fn test_new() {
311        assert!(WebSocket::new("ws://localhost").is_ok());
312
313        match WebSocket::new("bad url") {
314            Err(CreationError::SyntaxError(_)) => (),
315            v => panic!("expected SyntaxError, got {:?}", v),
316        }
317    }
318
319    #[test]
320    fn test_close() {
321        let socket = WebSocket::new("ws://localhost").unwrap();
322
323        socket.close();
324
325        assert!(socket.close_with_status( SocketCloseCode::NORMAL_CLOSURE, "closed" ).is_ok());
326
327        // Invalid close code
328        match socket.close_with_status( SocketCloseCode(12345), "closed" ) {
329            Err(CloseError::InvalidAccessError(_)) => (),
330            v => panic!("expected InvalidAccessError, got {:?}", v),
331        }
332
333        // Close reason too long (>123 bytes according to spec)
334        match socket.close_with_status(
335            SocketCloseCode::NORMAL_CLOSURE,
336            &(0..200).map(|_| "X").collect::<String>()
337        ) {
338            Err(CloseError::SyntaxError(_)) => (),
339            v => panic!("expected SyntaxError, got {:?}", v),
340        }
341    }
342}