added debugger for websockets, methods, created boilerplate production for websocket methods, added reactor
This commit is contained in:
parent
ff8e103274
commit
7c34ab3751
|
@ -0,0 +1,93 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Debugger</title>
|
||||
<style>
|
||||
html {
|
||||
font-family:system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
margin: 0;
|
||||
background-color: #EFEFE6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.big_block {
|
||||
min-height: 50px !important;
|
||||
}
|
||||
|
||||
.block {
|
||||
margin: 12px;
|
||||
padding: 8px;
|
||||
border: 1px solid #343434;
|
||||
border-radius: 0.45em;
|
||||
min-height: 12px;
|
||||
}
|
||||
|
||||
.vflex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hflex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 4px;
|
||||
padding: 4px;
|
||||
border: 1px solid #343434;
|
||||
border-radius: 0.45em;
|
||||
text-align: center;
|
||||
text-justify: center;
|
||||
}
|
||||
|
||||
.bg_primary {
|
||||
background-color: #F9F9F9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="vflex">
|
||||
<div class="block bg_primary vflex big_block">
|
||||
<h1>Welcome to Heimdall Debugger</h1>
|
||||
</div>
|
||||
<div class="block bg_primary vflex big_block" id="content-box">
|
||||
</div>
|
||||
<div class="block bg_primary vflex big_block">
|
||||
<div class="block bg_primary vflex">
|
||||
<textarea id="description-box"></textarea>
|
||||
</div>
|
||||
<div class="block bg_primary">
|
||||
<button onclick="send();">Add description</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
secure = '';
|
||||
if (location.protocol === 'https:') {
|
||||
secure = 's';
|
||||
};
|
||||
var content_box = document.getElementById('content-box');
|
||||
var description_box = document.getElementById('description-box');
|
||||
var socket = new WebSocket(`ws${secure}://${location.host}/socket`);
|
||||
socket.addEventListener('message', (message) => {
|
||||
let content = JSON.parse(message.data);
|
||||
console.log(content);
|
||||
let element = document.createElement('div');
|
||||
element.innerText = message.data;
|
||||
element.classList.add("block", "bg_primary", "vflex", "big_block");
|
||||
content_box.appendChild(element);
|
||||
});
|
||||
function send() {
|
||||
if (socket.readyState != WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
socket.send(description_box.value);
|
||||
};
|
||||
</script>
|
||||
</html>
|
38
proto.rb
38
proto.rb
|
@ -26,7 +26,7 @@ module Heimdall
|
|||
# @return [self]
|
||||
def new(*args, **params)
|
||||
object = super(*args, **params)
|
||||
@uuids[object.uuid] = object
|
||||
@uuids[object.uuid] = WeakRef.new(object)
|
||||
@last = object.uuid
|
||||
add_foreign(object.foreign_ids, object)
|
||||
object
|
||||
|
@ -75,7 +75,7 @@ module Heimdall
|
|||
# @return [void]
|
||||
def add_foreign(foreign, obj)
|
||||
foreign.each do |f_id|
|
||||
@foreign_ids[check_foreign(f_id)] = obj
|
||||
@foreign_ids[check_foreign(f_id)] = WeakRef.new(obj)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -137,7 +137,7 @@ module Heimdall
|
|||
# Add a listener to the PubSub
|
||||
# @param listener [#call]
|
||||
# @return [void]
|
||||
def listen(listener)
|
||||
def listen(&listener)
|
||||
@listeners.append(listener)
|
||||
end
|
||||
|
||||
|
@ -159,8 +159,16 @@ module Heimdall
|
|||
class Message < UUIDObject
|
||||
def initialize(datahash, **params)
|
||||
@id = datahash["id"]
|
||||
@from = UUIDObject.get(datahash["from"])
|
||||
@to = UUIDObject.get(datahash["to"])
|
||||
@from = if datahash["from"]
|
||||
UUIDObject.get(datahash["from"])
|
||||
elsif datahash["from_foreign"]
|
||||
UUIDObject.get_foreign(datahash["from_foreign"])
|
||||
end
|
||||
@to = if datahash["to"]
|
||||
UUIDObject.get(datahash["to"])
|
||||
elsif datahash["to_foreign"]
|
||||
UUIDObject.get_foreign(datahash["to_foreign"])
|
||||
end
|
||||
@content = datahash["content"]
|
||||
# @reply_to = datahash["reply_to"] # TODO: make this make sense
|
||||
@attachments = datahash["attachments"]
|
||||
|
@ -168,6 +176,21 @@ module Heimdall
|
|||
super(**params)
|
||||
end
|
||||
|
||||
# Convert message data to a JSON struct
|
||||
# @return [String] JSON struct
|
||||
def to_struct
|
||||
JSON.dump({
|
||||
"id" => @id,
|
||||
"uuid" => @uuid,
|
||||
"from" => @from.uuid,
|
||||
"to" => @to.uuid,
|
||||
"from_foreign" => @from.foreing_ids,
|
||||
"to_foreign" => @to.foreign_ids,
|
||||
"content" => @content,
|
||||
"attachments" => @attachments
|
||||
})
|
||||
end
|
||||
|
||||
attr_reader :from, :to, :content, :reply_to, :attachments
|
||||
end
|
||||
|
||||
|
@ -189,6 +212,7 @@ module Heimdall
|
|||
def to_struct
|
||||
JSON.dump({
|
||||
"id" => @id,
|
||||
"uuid" => @uuid,
|
||||
"username" => @username,
|
||||
"nickname" => @nickname,
|
||||
"avatar" => @avatar
|
||||
|
@ -201,6 +225,7 @@ module Heimdall
|
|||
DEFAULT_AVATAR = ""
|
||||
|
||||
def initialize(datahash, **params)
|
||||
@pubsub = PubSub.new
|
||||
@id = datahash["id"]
|
||||
@name = datahash["name"] or @id
|
||||
@avatar = datahash["avatar"] || self.class::DEFAULT_AVATAR
|
||||
|
@ -213,10 +238,13 @@ module Heimdall
|
|||
def to_struct
|
||||
JSON.dump({
|
||||
"id" => @id,
|
||||
"uuid" => @uuid,
|
||||
"name" => @name,
|
||||
"avatar" => @avatar
|
||||
})
|
||||
end
|
||||
|
||||
attr_reader :pubsub
|
||||
end
|
||||
|
||||
VERSION = "1.0"
|
||||
|
|
176
server.ru
176
server.ru
|
@ -3,8 +3,171 @@
|
|||
require_relative '.env'
|
||||
require_relative 'proto'
|
||||
require 'landline'
|
||||
require 'landline/extensions/websocket'
|
||||
require 'json'
|
||||
|
||||
# Class for handling websocket connection requests
|
||||
class Reactor
|
||||
def initialize
|
||||
@sockets = []
|
||||
@events = {}
|
||||
end
|
||||
|
||||
# Attach websocket to reactor
|
||||
# @param socket [Landline::WebSocket::WSockWrapper]
|
||||
def attach(socket)
|
||||
@sockets.append(socket)
|
||||
end
|
||||
|
||||
# Push data to all attached sockets
|
||||
# @param data [Array,Hash]
|
||||
def push(data)
|
||||
@sockets.each { |x| x.write(data) }
|
||||
end
|
||||
|
||||
# Handle websocket event
|
||||
# @param event [String]
|
||||
# @param block [#call]
|
||||
def on(event, &block)
|
||||
@events[event] ||= []
|
||||
@events[event].append(block)
|
||||
end
|
||||
|
||||
# Main loop
|
||||
def listen_loop
|
||||
loop do
|
||||
readable = IO.select(@sockets, nil, nil, 5)&.fetch(0, nil)
|
||||
readable&.each do |socket|
|
||||
output = socket.read
|
||||
if output
|
||||
_respond(socket, output)
|
||||
else
|
||||
@sockets.delete(socket)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _respond(socket, output)
|
||||
data = JSON.parse(output.data)
|
||||
event_name = data['event']
|
||||
@events[event_name]&.each do |callback|
|
||||
callback.call(socket,
|
||||
*(data['args'] || []),
|
||||
**(data['params'] || {}))
|
||||
end
|
||||
rescue JSON::ParserError => e
|
||||
socket.write(JSON.dump({ error: e.message, code: 400 }))
|
||||
end
|
||||
end
|
||||
|
||||
# Class acting as session-persistent object storage
|
||||
class ObjectStorage
|
||||
def initialize
|
||||
@channels = {}
|
||||
@users = {}
|
||||
end
|
||||
|
||||
# Add channel to object storage
|
||||
# @param channel [Heimdall::Channel]
|
||||
def add_channel(channel)
|
||||
@channels[channel.uuid] = channel
|
||||
end
|
||||
|
||||
# Add user to object storage
|
||||
# @param user [Heimdall::User]
|
||||
def add_user(user)
|
||||
@users[user.uuid] = user
|
||||
end
|
||||
|
||||
attr_reader :channels, :users
|
||||
end
|
||||
|
||||
OBJECT_STORAGE = ObjectStorage.new
|
||||
|
||||
# Helper methods for building reactor listeners
|
||||
module ReactorHelpers
|
||||
# Create a block for foreign and uuid indexing of a class
|
||||
# @param cls [Class]
|
||||
# @param block [#call]
|
||||
# @param reactor [Reactor]
|
||||
def self.foreign_and_uuid(method_name, reactor, cls, &block)
|
||||
reactor.on "#{cls.name.downcase}.#{method_name}.foreign" do |socket, f_id,
|
||||
*args, **vars|
|
||||
obj = cls.get_foreign(f_id)
|
||||
block.call(socket, obj, *args, **vars)
|
||||
end
|
||||
reactor.on "#{cls.name.downcase}.#{method_name}.uuid" do |socket, uuid,
|
||||
*args, **vars|
|
||||
obj = cls.get_uuid(uuid)
|
||||
block.call(socket, obj, *args, **vars)
|
||||
end
|
||||
end
|
||||
|
||||
# Boilerplate get/delete functions for a class
|
||||
# @param reactor [Reactor]
|
||||
# @param cls [Class]
|
||||
def self.foreign_and_uuid_boilerplate(reactor, cls)
|
||||
cname = cls.name.gsub(/\A.+::/, "").downcase
|
||||
reactor.on "#{cname}.get.foreign" do |socket, f_id|
|
||||
obj = cls.get_foreign(f_id)
|
||||
socket.write(obj || JSON.dump({ error: "#{cname} not found", code: 400 }))
|
||||
end
|
||||
reactor.on "#{cname}.get.uuid" do |socket, uuid|
|
||||
obj = cls.get_uuid(uuid)
|
||||
socket.write(obj || JSON.dump({ error: "#{cname} not found", code: 400 }))
|
||||
end
|
||||
reactor.on "#{cname}.delete.foreign" do |socket, f_id|
|
||||
status = cls.delete_foreign(f_id)
|
||||
socket.write(JSON.dump(if status
|
||||
{ code: 200 }
|
||||
else
|
||||
{ error: "#{cname} not found", code: 400 }
|
||||
end))
|
||||
end
|
||||
reactor.on "#{cname}.delete.uuid" do |socket, f_id|
|
||||
status = cls.delete_uuid(f_id)
|
||||
socket.write(JSON.dump(if status
|
||||
{ code: 200 }
|
||||
else
|
||||
{ error: "#{cname} not found", code: 400 }
|
||||
end))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# WebSocket handling
|
||||
WEBSOCKET_LISTENER = Reactor.new.tap do |react|
|
||||
react.on "version" do |socket|
|
||||
socket.write(JSON.dump({
|
||||
"version" => Heimdall::VERSION
|
||||
}))
|
||||
end
|
||||
# --- USER ---
|
||||
ReactorHelpers.foreign_and_uuid_boilerplate(react, Heimdall::User)
|
||||
# --- CHANNEL ---
|
||||
ReactorHelpers.foreign_and_uuid_boilerplate(react, Heimdall::Channel)
|
||||
ReactorHelpers.foreign_and_uuid("listen", react,
|
||||
Heimdall::Channel) do |socket, channel|
|
||||
channel.pubsub.listen do |msg|
|
||||
socket.write(msg)
|
||||
end
|
||||
socket.write(JSON.dump({ code: 200 }))
|
||||
end
|
||||
ReactorHelpers.foreign_and_uuid("send", react,
|
||||
Heimdall::Channel) do |socket, channel,
|
||||
**message_args|
|
||||
msg = Heimdall::Message.new(message_args)
|
||||
channel.pubsub.push(msg.to_struct)
|
||||
socket.write(JSON.dump({ code: 200,
|
||||
msg_uuid: msg.uuid }))
|
||||
end
|
||||
end
|
||||
|
||||
WEBSOCKET_LISTENER_LOOP = Thread.new { WEBSOCKET_LISTENER.listen_loop }
|
||||
|
||||
# Primary server class
|
||||
class HeimdallServer < Landline::App
|
||||
before do
|
||||
|
@ -48,6 +211,13 @@ class HeimdallServer < Landline::App
|
|||
})
|
||||
end
|
||||
|
||||
root __dir__
|
||||
serve "*.html"
|
||||
|
||||
get "/debug" do
|
||||
jump "/debug.html"
|
||||
end
|
||||
|
||||
pipeline do |request, &output|
|
||||
output.call(request)
|
||||
rescue Heimdall::ProtoError => e
|
||||
|
@ -94,6 +264,7 @@ class HeimdallServer < Landline::App
|
|||
"nickname" => [String, NilClass],
|
||||
"avatar" => [String, NilClass])
|
||||
new_user = Heimdall::User.new(@data)
|
||||
OBJECT_STORAGE.add_user(new_user)
|
||||
JSON.dump({ "uuid": new_user.uuid })
|
||||
end
|
||||
|
||||
|
@ -106,12 +277,17 @@ class HeimdallServer < Landline::App
|
|||
"name" => String,
|
||||
"avatar" => [String, NilClass])
|
||||
new_channel = Heimdall::Channel.new(@data)
|
||||
OBJECT_STORAGE.add_channel(new_channel)
|
||||
JSON.dump({ "uuid": new_channel.uuid })
|
||||
end
|
||||
|
||||
instance_exec(Heimdall::Channel, &read_delete)
|
||||
end
|
||||
|
||||
websocket "/socket" do |socket|
|
||||
WEBSOCKET_LISTENER.attach(socket)
|
||||
end
|
||||
|
||||
handle do |status, backtrace: nil, **_|
|
||||
backtrace ||= [Landline::Util::HTTP_STATUS[status]]
|
||||
[status,
|
||||
|
|
Loading…
Reference in New Issue