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.
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
- #__transport_closed__(code = 1000, reason = "", was_clean: true) ⇒ Object
- #__transport_error__ ⇒ Object
- #__transport_message__(data) ⇒ Object
-
#__transport_open__(protocol = nil) ⇒ Object
--- Transport callbacks --------------------------------------- A connector-provided transport reports the connection lifecycle through these, ON THE PAGE THREAD (a threaded transport marshals via scheduler.post_external).
-
#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
#__dommy_dump_event_failure__, #__internal_deliver_event__, #__internal_process_event_handler_return__, #add_event_listener, capture_flag, #deliver_at, #dispatch_event, #event_name_from_on, #invoke_listener_isolated, js_truthy?, #on_handler, #remove_event_listener, #set_on_handler
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 50 51 52 53 54 55 56 |
# 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 = {} # A host-installed connector (Dommy::Rack wires real in-process # connections through it) owns the connection when it returns a # transport; otherwise fall back to the in-memory stub, which # auto-opens via microtask unless tests disable. connector = window.respond_to?(:websocket_connector) ? window.websocket_connector : nil @transport = connector&.call(self, @url, @requested_protocols) return if @transport 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
227 228 229 |
# File 'lib/dommy/web_socket.rb', line 227 def __internal_event_parent__ nil end |
#__js_call__(method, args) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/dommy/web_socket.rb', line 212 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 -------------------------------------------------
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/dommy/web_socket.rb', line 171 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
198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/dommy/web_socket.rb', line 198 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 ------------------------------------------------
134 135 136 |
# File 'lib/dommy/web_socket.rb', line 134 def @sent_messages.dup end |
#__test_simulate_close__(code = 1000, reason = "", was_clean: true) ⇒ Object
153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/dommy/web_socket.rb', line 153 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
165 166 167 |
# File 'lib/dommy/web_socket.rb', line 165 def __test_simulate_error__ dispatch_event(Event.new("error")) end |
#__test_simulate_message__(data) ⇒ Object
147 148 149 150 151 |
# File 'lib/dommy/web_socket.rb', line 147 def (data) return if @ready_state != OPEN dispatch_event(MessageEvent.new("message", "data" => data)) end |
#__test_simulate_open__ ⇒ Object
138 139 140 141 142 143 144 145 |
# File 'lib/dommy/web_socket.rb', line 138 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 |
#__transport_closed__(code = 1000, reason = "", was_clean: true) ⇒ Object
122 123 124 125 126 |
# File 'lib/dommy/web_socket.rb', line 122 def __transport_closed__(code = 1000, reason = "", was_clean: true) return if @ready_state == CLOSED __test_simulate_close__(code, reason, was_clean: was_clean) end |
#__transport_error__ ⇒ Object
128 129 130 |
# File 'lib/dommy/web_socket.rb', line 128 def __transport_error__ __test_simulate_error__ end |
#__transport_message__(data) ⇒ Object
118 119 120 |
# File 'lib/dommy/web_socket.rb', line 118 def (data) (data) end |
#__transport_open__(protocol = nil) ⇒ Object
--- Transport callbacks --------------------------------------- A connector-provided transport reports the connection lifecycle through these, ON THE PAGE THREAD (a threaded transport marshals via scheduler.post_external). They share the state machine with the test seams so stub-driven and transport-driven sockets behave identically.
109 110 111 112 113 114 115 116 |
# File 'lib/dommy/web_socket.rb', line 109 def __transport_open__(protocol = nil) @protocol = protocol.to_s unless protocol.nil? return if @ready_state != CONNECTING @ready_state = OPEN @protocol = @requested_protocols.first || "" if @protocol.empty? && protocol.nil? 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.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/dommy/web_socket.rb', line 78 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 if @transport # The transport completes the closing handshake and reports back via # __transport_closed__ (which fires the close event). @transport.close(final_code, final_reason) else @window.scheduler.queue_microtask(proc { __test_simulate_close__(final_code, final_reason) }) end nil end |
#send(data) ⇒ Object
65 66 67 68 69 70 71 72 73 74 |
# File 'lib/dommy/web_socket.rb', line 65 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 @transport&.send_text(data.to_s) nil end |