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}