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]
|
# @return [self]
|
||||||
def new(*args, **params)
|
def new(*args, **params)
|
||||||
object = super(*args, **params)
|
object = super(*args, **params)
|
||||||
@uuids[object.uuid] = object
|
@uuids[object.uuid] = WeakRef.new(object)
|
||||||
@last = object.uuid
|
@last = object.uuid
|
||||||
add_foreign(object.foreign_ids, object)
|
add_foreign(object.foreign_ids, object)
|
||||||
object
|
object
|
||||||
|
@ -75,7 +75,7 @@ module Heimdall
|
||||||
# @return [void]
|
# @return [void]
|
||||||
def add_foreign(foreign, obj)
|
def add_foreign(foreign, obj)
|
||||||
foreign.each do |f_id|
|
foreign.each do |f_id|
|
||||||
@foreign_ids[check_foreign(f_id)] = obj
|
@foreign_ids[check_foreign(f_id)] = WeakRef.new(obj)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ module Heimdall
|
||||||
# Add a listener to the PubSub
|
# Add a listener to the PubSub
|
||||||
# @param listener [#call]
|
# @param listener [#call]
|
||||||
# @return [void]
|
# @return [void]
|
||||||
def listen(listener)
|
def listen(&listener)
|
||||||
@listeners.append(listener)
|
@listeners.append(listener)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -159,8 +159,16 @@ module Heimdall
|
||||||
class Message < UUIDObject
|
class Message < UUIDObject
|
||||||
def initialize(datahash, **params)
|
def initialize(datahash, **params)
|
||||||
@id = datahash["id"]
|
@id = datahash["id"]
|
||||||
@from = UUIDObject.get(datahash["from"])
|
@from = if datahash["from"]
|
||||||
@to = UUIDObject.get(datahash["to"])
|
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"]
|
@content = datahash["content"]
|
||||||
# @reply_to = datahash["reply_to"] # TODO: make this make sense
|
# @reply_to = datahash["reply_to"] # TODO: make this make sense
|
||||||
@attachments = datahash["attachments"]
|
@attachments = datahash["attachments"]
|
||||||
|
@ -168,6 +176,21 @@ module Heimdall
|
||||||
super(**params)
|
super(**params)
|
||||||
end
|
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
|
attr_reader :from, :to, :content, :reply_to, :attachments
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -189,6 +212,7 @@ module Heimdall
|
||||||
def to_struct
|
def to_struct
|
||||||
JSON.dump({
|
JSON.dump({
|
||||||
"id" => @id,
|
"id" => @id,
|
||||||
|
"uuid" => @uuid,
|
||||||
"username" => @username,
|
"username" => @username,
|
||||||
"nickname" => @nickname,
|
"nickname" => @nickname,
|
||||||
"avatar" => @avatar
|
"avatar" => @avatar
|
||||||
|
@ -201,6 +225,7 @@ module Heimdall
|
||||||
DEFAULT_AVATAR = ""
|
DEFAULT_AVATAR = ""
|
||||||
|
|
||||||
def initialize(datahash, **params)
|
def initialize(datahash, **params)
|
||||||
|
@pubsub = PubSub.new
|
||||||
@id = datahash["id"]
|
@id = datahash["id"]
|
||||||
@name = datahash["name"] or @id
|
@name = datahash["name"] or @id
|
||||||
@avatar = datahash["avatar"] || self.class::DEFAULT_AVATAR
|
@avatar = datahash["avatar"] || self.class::DEFAULT_AVATAR
|
||||||
|
@ -213,10 +238,13 @@ module Heimdall
|
||||||
def to_struct
|
def to_struct
|
||||||
JSON.dump({
|
JSON.dump({
|
||||||
"id" => @id,
|
"id" => @id,
|
||||||
|
"uuid" => @uuid,
|
||||||
"name" => @name,
|
"name" => @name,
|
||||||
"avatar" => @avatar
|
"avatar" => @avatar
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr_reader :pubsub
|
||||||
end
|
end
|
||||||
|
|
||||||
VERSION = "1.0"
|
VERSION = "1.0"
|
||||||
|
|
176
server.ru
176
server.ru
|
@ -3,8 +3,171 @@
|
||||||
require_relative '.env'
|
require_relative '.env'
|
||||||
require_relative 'proto'
|
require_relative 'proto'
|
||||||
require 'landline'
|
require 'landline'
|
||||||
|
require 'landline/extensions/websocket'
|
||||||
require 'json'
|
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
|
# Primary server class
|
||||||
class HeimdallServer < Landline::App
|
class HeimdallServer < Landline::App
|
||||||
before do
|
before do
|
||||||
|
@ -48,6 +211,13 @@ class HeimdallServer < Landline::App
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
root __dir__
|
||||||
|
serve "*.html"
|
||||||
|
|
||||||
|
get "/debug" do
|
||||||
|
jump "/debug.html"
|
||||||
|
end
|
||||||
|
|
||||||
pipeline do |request, &output|
|
pipeline do |request, &output|
|
||||||
output.call(request)
|
output.call(request)
|
||||||
rescue Heimdall::ProtoError => e
|
rescue Heimdall::ProtoError => e
|
||||||
|
@ -94,6 +264,7 @@ class HeimdallServer < Landline::App
|
||||||
"nickname" => [String, NilClass],
|
"nickname" => [String, NilClass],
|
||||||
"avatar" => [String, NilClass])
|
"avatar" => [String, NilClass])
|
||||||
new_user = Heimdall::User.new(@data)
|
new_user = Heimdall::User.new(@data)
|
||||||
|
OBJECT_STORAGE.add_user(new_user)
|
||||||
JSON.dump({ "uuid": new_user.uuid })
|
JSON.dump({ "uuid": new_user.uuid })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -106,12 +277,17 @@ class HeimdallServer < Landline::App
|
||||||
"name" => String,
|
"name" => String,
|
||||||
"avatar" => [String, NilClass])
|
"avatar" => [String, NilClass])
|
||||||
new_channel = Heimdall::Channel.new(@data)
|
new_channel = Heimdall::Channel.new(@data)
|
||||||
|
OBJECT_STORAGE.add_channel(new_channel)
|
||||||
JSON.dump({ "uuid": new_channel.uuid })
|
JSON.dump({ "uuid": new_channel.uuid })
|
||||||
end
|
end
|
||||||
|
|
||||||
instance_exec(Heimdall::Channel, &read_delete)
|
instance_exec(Heimdall::Channel, &read_delete)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
websocket "/socket" do |socket|
|
||||||
|
WEBSOCKET_LISTENER.attach(socket)
|
||||||
|
end
|
||||||
|
|
||||||
handle do |status, backtrace: nil, **_|
|
handle do |status, backtrace: nil, **_|
|
||||||
backtrace ||= [Landline::Util::HTTP_STATUS[status]]
|
backtrace ||= [Landline::Util::HTTP_STATUS[status]]
|
||||||
[status,
|
[status,
|
||||||
|
|
Loading…
Reference in New Issue