Class: Tina4::WebSocketConnection

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/websocket.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id, socket, ws_server: nil, path: "/") ⇒ WebSocketConnection

Returns a new instance of WebSocketConnection.



661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
# File 'lib/tina4/websocket.rb', line 661

def initialize(id, socket, ws_server: nil, path: "/")
  @id = id
  @socket = socket
  @params = {}
  @ws_server = ws_server
  @path = path
  # Verified JWT payload on a secured WS route, else nil (public route).
  # Mirrors Python's connection.auth.
  @auth = nil
  @rooms = Set.new
  @on_message_handler = nil
  @on_close_handler = nil
  @on_error_handler = nil
  # Updated on every inbound frame; the idle reaper closes connections that
  # have been silent longer than TINA4_WS_IDLE_TIMEOUT (opt-in).
  @last_activity = Time.now.to_f
end

Instance Attribute Details

#authObject

Returns the value of attribute auth.



658
659
660
# File 'lib/tina4/websocket.rb', line 658

def auth
  @auth
end

#idObject (readonly)

Returns the value of attribute id.



657
658
659
# File 'lib/tina4/websocket.rb', line 657

def id
  @id
end

#last_activityObject (readonly)

Returns the value of attribute last_activity.



657
658
659
# File 'lib/tina4/websocket.rb', line 657

def last_activity
  @last_activity
end

#on_close_handlerObject

Returns the value of attribute on_close_handler.



658
659
660
# File 'lib/tina4/websocket.rb', line 658

def on_close_handler
  @on_close_handler
end

#on_error_handlerObject

Returns the value of attribute on_error_handler.



658
659
660
# File 'lib/tina4/websocket.rb', line 658

def on_error_handler
  @on_error_handler
end

#on_message_handlerObject

Returns the value of attribute on_message_handler.



658
659
660
# File 'lib/tina4/websocket.rb', line 658

def on_message_handler
  @on_message_handler
end

#paramsObject

Returns the value of attribute params.



658
659
660
# File 'lib/tina4/websocket.rb', line 658

def params
  @params
end

#pathObject

Returns the value of attribute path.



658
659
660
# File 'lib/tina4/websocket.rb', line 658

def path
  @path
end

#roomsObject (readonly)

Returns the value of attribute rooms.



657
658
659
# File 'lib/tina4/websocket.rb', line 657

def rooms
  @rooms
end

Instance Method Details

#broadcast(message, include_self: false) ⇒ Object

Broadcast a message to all other connections on the same path



712
713
714
715
716
717
718
719
720
# File 'lib/tina4/websocket.rb', line 712

def broadcast(message, include_self: false)
  return unless @ws_server

  @ws_server.connections.each do |cid, conn|
    next if !include_self && cid == @id
    next if conn.path != @path
    conn.send_text(message)
  end
end

#broadcast_to_room(room_name, message, exclude_self: false) ⇒ Object



704
705
706
707
708
709
# File 'lib/tina4/websocket.rb', line 704

def broadcast_to_room(room_name, message, exclude_self: false)
  return unless @ws_server

  exclude = exclude_self ? @id : nil
  @ws_server.broadcast_to_room(room_name, message, exclude: exclude)
end

#build_frame(opcode, data) ⇒ Object



790
791
792
# File 'lib/tina4/websocket.rb', line 790

def build_frame(opcode, data)
  Tina4.build_frame(opcode, data)
end

#close(code: 1000, reason: "") ⇒ Object



754
755
756
757
758
759
# File 'lib/tina4/websocket.rb', line 754

def close(code: 1000, reason: "")
  payload = [code].pack("n") + reason
  frame = build_frame(0x8, payload)
  @socket.write(frame) rescue nil
  @socket.close rescue nil
end

#closed?Boolean

True once a write has failed (broken pipe / closed socket). The manager's resilient broadcast path uses this to prune dead connections.

Returns:

  • (Boolean)


724
725
726
# File 'lib/tina4/websocket.rb', line 724

def closed?
  @closed == true
end

#join_room(room_name) ⇒ Object



694
695
696
697
# File 'lib/tina4/websocket.rb', line 694

def join_room(room_name)
  @rooms.add(room_name)
  @ws_server&._join_room(@id, room_name)
end

#leave_room(room_name) ⇒ Object



699
700
701
702
# File 'lib/tina4/websocket.rb', line 699

def leave_room(room_name)
  @rooms.delete(room_name)
  @ws_server&._leave_room(@id, room_name)
end

#on_close(&block) ⇒ Object

Register a close handler (decorator style, matching Python).



690
691
692
# File 'lib/tina4/websocket.rb', line 690

def on_close(&block)
  @on_close_handler = block
end

#on_message(&block) ⇒ Object

Register a message handler (decorator style, matching Python).



685
686
687
# File 'lib/tina4/websocket.rb', line 685

def on_message(&block)
  @on_message_handler = block
end

#read_frameObject



761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
# File 'lib/tina4/websocket.rb', line 761

def read_frame
  first_byte = @socket.getbyte
  return nil unless first_byte

  opcode = first_byte & 0x0F
  second_byte = @socket.getbyte
  return nil unless second_byte

  masked = (second_byte & 0x80) != 0
  length = second_byte & 0x7F

  if length == 126
    length = @socket.read(2).unpack1("n")
  elsif length == 127
    length = @socket.read(8).unpack1("Q>")
  end

  mask_key = masked ? @socket.read(4).bytes : nil
  data = @socket.read(length) || ""

  if masked && mask_key
    data = data.bytes.each_with_index.map { |b, i| b ^ mask_key[i % 4] }.pack("C*")
  end

  { opcode: opcode, data: data }
rescue IOError, EOFError
  nil
end

#send(message) ⇒ Object Also known as: send_text



728
729
730
731
732
733
734
735
736
737
# File 'lib/tina4/websocket.rb', line 728

def send(message)
  data = message.is_a?(String) ? message : message.to_s
  # Text frames must be valid UTF-8; binary payloads are sent verbatim.
  data = data.encode("UTF-8") if data.encoding != Encoding::ASCII_8BIT
  frame = build_frame(0x1, data)
  @socket.write(frame)
rescue IOError
  # Connection closed — mark dead so the broadcast path prunes it.
  @closed = true
end

#send_json(data) ⇒ Object

Serialize a Hash/Array (or any JSON-coercible value) and send as a text frame. Matches Python/PHP send_json.



743
744
745
# File 'lib/tina4/websocket.rb', line 743

def send_json(data)
  send_text(JSON.generate(data))
end

#send_pong(data) ⇒ Object



747
748
749
750
751
752
# File 'lib/tina4/websocket.rb', line 747

def send_pong(data)
  frame = build_frame(0xA, data || "")
  @socket.write(frame)
rescue IOError
  @closed = true
end

#touchObject

Mark inbound activity for the idle reaper.



680
681
682
# File 'lib/tina4/websocket.rb', line 680

def touch
  @last_activity = Time.now.to_f
end