Class: DuoRuby::Socket

Inherits:
Channel show all
Includes:
Transport
Defined in:
lib/duoruby/socket.rb,
lib/duoruby/socket/transport.rb,
lib/duoruby/socket/test_promise.rb

Overview

Browser-side event hub. Manages the WebSocket connection and message dispatch.

Socket inherits the full Channel event system. Declare handlers at the class level (inherited by subclasses) or add them at runtime on an instance.

Unlike server handlers, Socket event handlers receive only the message params as keyword arguments — there is no client positional argument because there is exactly one connection per browser socket instance.

A transport callable (proc or block) is responsible for delivering outbound messages. Under Opal it is set automatically by Transport#connect; in tests you can supply any callable at construction time.

Examples:

Inline transport for testing

delivered = []
socket = DuoRuby::Socket.new { |msg| delivered << msg }
socket.send(:join, room: "lobby")

Subclass with class-level handlers

class MySocket < DuoRuby::Socket
  on(:snapshot) { |rooms:, **| puts "rooms: #{rooms.join(', ')}" }
end

Defined Under Namespace

Modules: Transport Classes: TestPromise

Instance Attribute Summary collapse

Attributes inherited from Channel

#handlers

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Transport

#connect, included, #open_socket, #reconnect

Methods inherited from Channel

#channel, #dispatch, #handler_for, inherited, #off, #on, #one

Methods included from Channel::HandlerMethods

#channel, #handlers, included, #included, #off, #on, #one

Constructor Details

#initialize(transport: nil) {|message| ... } ⇒ Socket

Returns a new instance of Socket.

Parameters:

  • transport (Proc, nil) (defaults to: nil)

    callable that delivers outbound messages; mutually exclusive with the block form. May be omitted and set later by DuoRuby::Socket::Transport#connect.

Yield Parameters:

  • message (Hash)

    the serialized message to deliver



48
49
50
51
52
53
54
# File 'lib/duoruby/socket.rb', line 48

def initialize(transport: nil, &transport_block)
  super()
  @transport = transport || transport_block
  @sent = []
  @pending_calls = {}
  @next_call_id = 0
end

Instance Attribute Details

#sentArray<Hash> (readonly)

Returns every message sent through this socket, for inspection.

Returns:

  • (Array<Hash>)

    every message sent through this socket, for inspection



39
40
41
# File 'lib/duoruby/socket.rb', line 39

def sent
  @sent
end

#socketBrowser::Socket? (readonly)

Returns the active WebSocket, or nil before DuoRuby::Socket::Transport#connect.

Returns:



42
43
44
# File 'lib/duoruby/socket.rb', line 42

def socket
  @socket
end

Class Method Details

.promise_classObject



121
122
123
# File 'lib/duoruby/socket.rb', line 121

def self.promise_class
  defined?(::PromiseV2) ? ::PromiseV2 : TestPromise
end

Instance Method Details

#cancel_pending_calls(code: :disconnect, message: "connection closed", details: nil) ⇒ Object



114
115
116
117
118
119
# File 'lib/duoruby/socket.rb', line 114

def cancel_pending_calls(code: :disconnect, message: "connection closed", details: nil)
  error = ReplyError.new(code: code, message: message, details: details)
  @pending_calls.each_value { |promise| promise.reject(error) }
  @pending_calls.clear
  self
end

#deliver(message) ⇒ Object



108
109
110
111
112
# File 'lib/duoruby/socket.rb', line 108

def deliver(message)
  sent << message
  @transport.call(message) if @transport
  message
end

#receive(message) ⇒ self

Opens the WebSocket connection and wires socket lifecycle events.

Only available under Opal (requires Browser::Socket). Raises immediately on CRuby. Raises if already connected.

Sets up a JSON-serialising transport and forwards:

  • socket :open → triggers :$connect

  • socket :message → calls #receive with the parsed JSON payload

  • socket :close → triggers :$disconnect

Coerces message and dispatches it to the appropriate event handlers. Params are forwarded as keyword arguments only (no positional client arg).

Parameters:

  • url (String, nil)

    the full WebSocket URL; defaults to the value returned by default_socket_url

  • path (String)

    the socket path used when url is not given

  • message (Message, Hash)

    the inbound message (raw parsed JSON or a Message)

Returns:

  • (self)

Raises:

  • (RuntimeError)

    if called outside Opal, or if already connected



94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/duoruby/socket.rb', line 94

def receive(message)
  message = Message.coerce(message)
  return resolve_call(message) if message.event == Message::REPLY_EVENT
  return reject_call(message) if message.event == Message::ERROR_EVENT && message.reply_to

  results = dispatch(message.event, **message.params)
  deliver(Message.reply(message.id, results.last).to_h) if message.id
  results
rescue StandardError => error
  raise unless message&.id

  deliver(Message.error(code: error.class.name, message: error.message, reply_to: message.id).to_h)
end

#send(event, **params) ⇒ Hash, PromiseV2

Sends event with params to the server.

Events ending with ? are questions: they include a request id and return a promise that resolves when the server replies. Other events are fire-and-forget and return the serialized message hash.

Parameters:

  • event (String, Symbol)

    the event name

  • params

    keyword arguments that become the message params

Returns:

  • (Hash, PromiseV2)

    the serialized message or reply promise



65
66
67
68
69
# File 'lib/duoruby/socket.rb', line 65

def send(event, **params)
  return send_question(event, **params) if question_event?(event)

  deliver(Message.new(event, **params).to_h)
end

#transport=(transport) ⇒ Object



71
72
73
# File 'lib/duoruby/socket.rb', line 71

def transport=(transport)
  @transport = transport
end