Class: Dommy::WebSocket
- Inherits:
-
Object
- Object
- Dommy::WebSocket
- Includes:
- Bridge::Methods, EventTarget
- Defined in:
- lib/dommy/web_socket.rb
Overview
‘WebSocket` polyfill. Real implementations open a TCP-then-frame connection; dommy exposes an in-memory transport tests drive via the `__*` seams:
ws.__test_simulate_open__ — fires `open`
ws.__test_simulate_message__(data) — fires `message`
ws.__test_simulate_close__(code, reason) — fires `close`
ws.__test_simulate_error__ — fires `error`
ws.__test_sent_messages__ — array of sent payloads
By default a ‘new WebSocket(url)` auto-opens via microtask so the common pattern (`ws.onopen = …; ws.send(…)`) works without extra setup.
Spec: websockets.spec.whatwg.org/
Defined Under Namespace
Classes: Error
Constant Summary collapse
- CONNECTING =
0- OPEN =
1- CLOSING =
2- CLOSED =
3- INLINE_HANDLERS =
%w[open message close error].freeze
Instance Attribute Summary collapse
-
#binary_type ⇒ Object
Returns the value of attribute binary_type.
-
#buffered_amount ⇒ Object
readonly
Returns the value of attribute buffered_amount.
-
#extensions ⇒ Object
readonly
Returns the value of attribute extensions.
-
#protocol ⇒ Object
readonly
Returns the value of attribute protocol.
-
#ready_state ⇒ Object
readonly
Returns the value of attribute ready_state.
-
#url ⇒ Object
readonly
Returns the value of attribute url.
Instance Method Summary collapse
- #__internal_event_parent__ ⇒ Object
- #__js_call__(method, args) ⇒ Object
-
#__js_get__(key) ⇒ Object
— JS bridge ————————————————-.
- #__js_set__(key, value) ⇒ Object
-
#__test_sent_messages__ ⇒ Object
— Test seams ————————————————.
- #__test_simulate_close__(code = 1000, reason = "", was_clean: true) ⇒ Object
- #__test_simulate_error__ ⇒ Object
- #__test_simulate_message__(data) ⇒ Object
- #__test_simulate_open__ ⇒ Object
-
#close(code = nil, reason = nil) ⇒ Object
close([code[, reason]]): code must be 1000 or in 3000–4999, and the UTF-8 reason must be ≤ 123 bytes, else throw — matching the WebSocket spec.
-
#initialize(window, url, protocols = nil) ⇒ WebSocket
constructor
A new instance of WebSocket.
- #send(data) ⇒ Object
Methods included from Bridge::Methods
Methods included from EventTarget
#__internal_deliver_event__, #add_event_listener, capture_flag, #deliver_at, #dispatch_event, js_truthy?, #remove_event_listener
Constructor Details
#initialize(window, url, protocols = nil) ⇒ WebSocket
Returns a new instance of WebSocket.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/dommy/web_socket.rb', line 32 def initialize(window, url, protocols = nil) @window = window @url = url.to_s @ready_state = CONNECTING @buffered_amount = 0 @extensions = "" @binary_type = "blob" # The subprotocol stays "" until the server selects one at the handshake; # remember what was requested and adopt the first on open. @requested_protocols = Array(protocols).flatten.map(&:to_s) @protocol = "" @sent_messages = [] @inline_handlers = {} # Auto-open via microtask unless tests disable. auto_open = window.globals["__ws_auto_open__"] @window.scheduler.queue_microtask(proc { __test_simulate_open__ }) unless auto_open == false end |
Instance Attribute Details
#binary_type ⇒ Object
Returns the value of attribute binary_type.
30 31 32 |
# File 'lib/dommy/web_socket.rb', line 30 def binary_type @binary_type end |
#buffered_amount ⇒ Object (readonly)
Returns the value of attribute buffered_amount.
29 30 31 |
# File 'lib/dommy/web_socket.rb', line 29 def buffered_amount @buffered_amount end |
#extensions ⇒ Object (readonly)
Returns the value of attribute extensions.
29 30 31 |
# File 'lib/dommy/web_socket.rb', line 29 def extensions @extensions end |
#protocol ⇒ Object (readonly)
Returns the value of attribute protocol.
29 30 31 |
# File 'lib/dommy/web_socket.rb', line 29 def protocol @protocol end |
#ready_state ⇒ Object (readonly)
Returns the value of attribute ready_state.
29 30 31 |
# File 'lib/dommy/web_socket.rb', line 29 def ready_state @ready_state end |
#url ⇒ Object (readonly)
Returns the value of attribute url.
29 30 31 |
# File 'lib/dommy/web_socket.rb', line 29 def url @url end |
Instance Method Details
#__internal_event_parent__ ⇒ Object
184 185 186 |
# File 'lib/dommy/web_socket.rb', line 184 def __internal_event_parent__ nil end |
#__js_call__(method, args) ⇒ Object
169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/dommy/web_socket.rb', line 169 def __js_call__(method, args) case method when "send" send(args[0]) when "close" close(args[0] || 1000, args[1] || "") when "addEventListener" add_event_listener(args[0], args[1], args[2]) when "removeEventListener" remove_event_listener(args[0], args[1], args[2]) when "dispatchEvent" dispatch_event(args[0]) end end |
#__js_get__(key) ⇒ Object
— JS bridge ————————————————-
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/dommy/web_socket.rb', line 128 def __js_get__(key) case key when "url" @url when "readyState" @ready_state when "bufferedAmount" @buffered_amount when "extensions" @extensions when "protocol" @protocol when "binaryType" @binary_type when "CONNECTING" CONNECTING when "OPEN" OPEN when "CLOSING" CLOSING when "CLOSED" CLOSED else @inline_handlers[inline_event_for(key)] end end |
#__js_set__(key, value) ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/dommy/web_socket.rb', line 155 def __js_set__(key, value) case key when "binaryType" self.binary_type = value else event = inline_event_for(key) set_inline_handler(event, value) if event end nil end |
#__test_sent_messages__ ⇒ Object
— Test seams ————————————————
91 92 93 |
# File 'lib/dommy/web_socket.rb', line 91 def @sent_messages.dup end |
#__test_simulate_close__(code = 1000, reason = "", was_clean: true) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/dommy/web_socket.rb', line 110 def __test_simulate_close__(code = 1000, reason = "", was_clean: true) @ready_state = CLOSED dispatch_event( CloseEvent.new( "close", "code" => code, "reason" => reason, "wasClean" => was_clean ) ) end |
#__test_simulate_error__ ⇒ Object
122 123 124 |
# File 'lib/dommy/web_socket.rb', line 122 def __test_simulate_error__ dispatch_event(Event.new("error")) end |
#__test_simulate_message__(data) ⇒ Object
104 105 106 107 108 |
# File 'lib/dommy/web_socket.rb', line 104 def (data) return if @ready_state != OPEN dispatch_event(MessageEvent.new("message", "data" => data)) end |
#__test_simulate_open__ ⇒ Object
95 96 97 98 99 100 101 102 |
# File 'lib/dommy/web_socket.rb', line 95 def __test_simulate_open__ return if @ready_state != CONNECTING @ready_state = OPEN # The handshake "selects" the first requested subprotocol. @protocol = @requested_protocols.first || "" dispatch_event(Event.new("open")) end |
#close(code = nil, reason = nil) ⇒ Object
close([code[, reason]]): code must be 1000 or in 3000–4999, and the UTF-8 reason must be ≤ 123 bytes, else throw — matching the WebSocket spec.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/dommy/web_socket.rb', line 70 def close(code = nil, reason = nil) unless code.nil? c = code.to_i unless c == 1000 || c.between?(3000, 4999) raise DOMException::InvalidAccessError, "The close code must be 1000 or in 3000-4999, got #{c}." end end if reason && reason.to_s.bytesize > 123 raise DOMException::SyntaxError, "The close reason must not exceed 123 UTF-8 bytes." end return if @ready_state == CLOSED || @ready_state == CLOSING @ready_state = CLOSING final_code = code.nil? ? 1005 : code.to_i final_reason = reason.to_s @window.scheduler.queue_microtask(proc { __test_simulate_close__(final_code, final_reason) }) nil end |
#send(data) ⇒ Object
58 59 60 61 62 63 64 65 66 |
# File 'lib/dommy/web_socket.rb', line 58 def send(data) # send() before the connection opens is an InvalidStateError (a # DOMException), not a bare Ruby error. raise DOMException::InvalidStateError, "WebSocket is not open" if @ready_state == CONNECTING return if @ready_state != OPEN # CLOSING/CLOSED silently discard (buffered) @sent_messages << data nil end |