class ChatChannel < Azu::Channel
@@rooms = Hash(String, Set(HTTP::WebSocket)).new { |h, k| h[k] = Set(HTTP::WebSocket).new }
@@socket_rooms = Hash(HTTP::WebSocket, String).new
ws "/chat"
def on_connect
send_to_client({
type: "connected",
message: "Send a 'join' message with room_id to join a room"
})
end
def on_message(message : String)
data = JSON.parse(message)
case data["type"]?.try(&.as_s)
when "join"
handle_join(data)
when "message"
handle_chat_message(data)
end
rescue
send_to_client({type: "error", message: "Invalid message"})
end
def on_close(code, message)
if room_id = @@socket_rooms[socket]?
@@rooms[room_id].delete(socket)
@@socket_rooms.delete(socket)
end
end
private def handle_join(data)
room_id = data["room_id"]?.try(&.as_s)
return send_to_client({type: "error", message: "room_id required"}) unless room_id
@@socket_rooms[socket] = room_id
@@rooms[room_id] << socket
send_to_client({type: "joined", room_id: room_id})
# Notify others in room
broadcast_to_room(room_id, {
type: "user_joined",
message: "A user joined the room"
}, exclude: socket)
end
private def handle_chat_message(data)
room_id = @@socket_rooms[socket]?
return send_to_client({type: "error", message: "Not in a room"}) unless room_id
message = data["message"]?.try(&.as_s)
return send_to_client({type: "error", message: "Message required"}) unless message
broadcast_to_room(room_id, {
type: "chat_message",
message: message,
timestamp: Time.utc.to_rfc3339
})
end
private def broadcast_to_room(room_id : String, data, exclude : HTTP::WebSocket? = nil)
message = data.to_json
@@rooms[room_id].each do |connection|
next if connection == exclude
spawn { connection.send(message) }
end
end
private def send_to_client(data)
socket.send(data.to_json)
end
end
def on_connect # Called when client connects
def on_message # Called when message received
def on_close # Called when client disconnects
# To all clients
CONNECTIONS.each { |s| spawn s.send(msg) }
# To specific room
@@rooms[room_id].each { |s| spawn s.send(msg) }
# Exclude sender
next if connection == exclude