This commit is contained in:
pjht 2023-10-06 17:02:48 -05:00
parent 0224abf215
commit 1b1598e668
Signed by: pjht
GPG Key ID: CA239FC6934E6F3A
13 changed files with 466 additions and 431 deletions

12
Gemfile
View File

@ -1,6 +1,8 @@
source "https://rubygems.org" # frozen_string_literal: true
gem "pry" source 'https://rubygems.org'
gem "web_gui", :path => "."
gem "dry-struct" gem 'dry-events'
gem "dry-events" gem 'dry-struct'
gem 'pry'
gem 'web_gui', path: '.'

View File

@ -1,5 +1,7 @@
require "bundler/gem_tasks" # frozen_string_literal: true
task :default => :build
require 'bundler/gem_tasks'
task default: :build
task :geminabox do task :geminabox do
`gem inabox -o pkg/web_gui-*.gem` `gem inabox -o pkg/web_gui-*.gem`
`rm pkg/web_gui-*.gem` `rm pkg/web_gui-*.gem`

View File

@ -1,11 +1,12 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup" require 'bundler/setup'
require "web_gui" require 'web_gui'
# You can add fixtures and/or initialization code here to make experimenting # You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like. # with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!) # (If you use this, don't forget to add pry to your Gemfile!)
require "pry" require 'pry'
Pry.start Pry.start

18
calc.rb
View File

@ -1,16 +1,18 @@
require "bundler/setup" # frozen_string_literal: true
require 'bundler/setup'
Bundler.setup Bundler.setup
require "web_gui" require 'web_gui'
$op = nil $op = nil
$num1 = nil $num1 = nil
$num2 = nil $num2 = nil
app=WebGui::App.new("Calculator") { app = WebGui::App.new('Calculator') do
opthash={:add=>"Add",:sub=>"Subtract",:mult=>"Multiply",:div=>"Divide"} opthash = { add: 'Add', sub: 'Subtract', mult: 'Multiply', div: 'Divide' }
add_element(WebGui::TextField.new { |val| $num1 = val.to_f }) add_element(WebGui::TextField.new { |val| $num1 = val.to_f })
add_element(WebGui::Menu.new(opthash) { |val| $op = val }) add_element(WebGui::Menu.new(opthash) { |val| $op = val })
add_element(WebGui::TextField.new { |val| $num2 = val.to_f }) add_element(WebGui::TextField.new { |val| $num2 = val.to_f })
res = nil res = nil
add_element(WebGui::ActionButton.new("Calculate") { add_element(WebGui::ActionButton.new('Calculate') do
case $op case $op
when :add when :add
result = $num1 + $num2 result = $num1 + $num2
@ -22,7 +24,7 @@ app=WebGui::App.new("Calculator") {
result = $num1.to_f / $num2 result = $num1.to_f / $num2
end end
res.settext("Result:#{result}") res.settext("Result:#{result}")
}) end)
res=add_element(WebGui::Text.new("Result:")) res = add_element(WebGui::Text.new('Result:'))
} end
app.run app.run

View File

@ -1,69 +1,71 @@
# frozen_string_literal: true
class Object class Object
def descendants def descendants
ObjectSpace.each_object(::Class).select { |klass| klass < self } ObjectSpace.each_object(::Class).select { |klass| klass < self }
end end
end end
module WebGui; end module WebGui; end
require "dry-struct" require 'dry-struct'
module Types module Types
include Dry::Types.module include Dry::Types.module
end end
require "dry-events" require 'dry-events'
require_relative "web_gui/element" require_relative 'web_gui/element'
require_relative "web_gui/event_manager" require_relative 'web_gui/event_manager'
require_relative "web_gui/app" require_relative 'web_gui/app'
require_relative "web_gui/window" require_relative 'web_gui/window'
require_relative "web_gui/serv" require_relative 'web_gui/serv'
require_relative "web_gui/wsserv" require_relative 'web_gui/wsserv'
module WebGui module WebGui
class Text < Element class Text < Element
attribute :text, Types::Coercible::String attribute :text, Types::Coercible::String
def render() def render
return "<p id=#{id}>#{text}</p>" "<p id=#{id}>#{text}</p>"
end end
def settext(text) def settext(text)
$app.update("paragraph",id,text) $app.update('paragraph', id, text)
end end
end end
class Link < Element class Link < Element
attribute :page, Types::Symbol attribute :page, Types::Symbol
attribute :text, Types::Coercible::String.optional attribute :text, Types::Coercible::String.optional
def render() def render
if text if text
return "<a href=\"#{page}\">#{text}</a>" "<a href=\"#{page}\">#{text}</a>"
else else
return "<a href=\"#{page}\">#{page.to_s}</a>" "<a href=\"#{page}\">#{page}</a>"
end end
end end
end end
class Button < Link class Button < Link
@css="a.button{-webkit-appearance: button;-moz-appearance: button;appearance: button;text-decoration: none;color: initial;padding: 0px 10px;}" @css = 'a.button{-webkit-appearance: button;-moz-appearance: button;appearance: button;text-decoration: none;color: initial;padding: 0px 10px;}'
def render() def render
return "<a href=\"#{page}\" class=\"button\">#{text}</a>" "<a href=\"#{page}\" class=\"button\">#{text}</a>"
end end
def self.css() class << self
return @css attr_reader :css
end end
end end
class Image < Element class Image < Element
attribute :name, Types::Coercible::String attribute :name, Types::Coercible::String
def render() def render
return "<img src=\"#{name}.jpg\"></img>" "<img src=\"#{name}.jpg\"></img>"
end end
end end
class Video < Element class Video < Element
attribute :name, Types::Coercible::String attribute :name, Types::Coercible::String
def render() def render
return "<video controls width=320 height=300><source src=\"#{name}.webm\" type=\"video/webm\"><source src=\"#{name}.mp4\" type=\"video/mp4\"></video>" "<video controls width=320 height=300><source src=\"#{name}.webm\" type=\"video/webm\"><source src=\"#{name}.mp4\" type=\"video/mp4\"></video>"
end end
end end
@ -74,8 +76,8 @@ module WebGui
@block = block @block = block
end end
def render() def render
return "<button id=#{id}>#{text}</button>" "<button id=#{id}>#{text}</button>"
end end
def on_button_pushed(event) def on_button_pushed(event)
@ -83,7 +85,6 @@ module WebGui
end end
end end
class Menu < Element class Menu < Element
attribute :opts, Types::Hash attribute :opts, Types::Hash
def initialize(opthash, &block) def initialize(opthash, &block)
@ -91,13 +92,13 @@ module WebGui
@block = block @block = block
end end
def render() def render
html = "<select id=#{id}>" html = "<select id=#{id}>"
opts.each do |val, text| opts.each do |val, text|
html+="<option value=\"#{val.to_s}\">#{text}</option>" html += "<option value=\"#{val}\">#{text}</option>"
end end
html+="</select>" html += '</select>'
return html html
end end
def on_menu_updated(event) def on_menu_updated(event)
@ -105,14 +106,13 @@ module WebGui
end end
end end
class TextField < Element class TextField < Element
def initialize(opthash, &block) def initialize(opthash, &block)
super(opthash) super(opthash)
@block = block @block = block
end end
def render() def render
html = "<input id=#{id}>" html = "<input id=#{id}>"
end end
@ -128,12 +128,12 @@ module WebGui
@block = block @block = block
end end
def render() def render
html="" html = ''
buttons.each do |value, text| buttons.each do |value, text|
html+="<input type=\"radio\" id=#{id} name=\"#{id}\" value=\"#{value.to_s}\">#{text}<br>" html += "<input type=\"radio\" id=#{id} name=\"#{id}\" value=\"#{value}\">#{text}<br>"
end end
return html html
end end
def on_radiobutton_updated(event) def on_radiobutton_updated(event)
@ -141,7 +141,6 @@ module WebGui
end end
end end
class CheckBox < Element class CheckBox < Element
attribute :boxes, Types::Coercible::Hash attribute :boxes, Types::Coercible::Hash
def initialize(opthash, &block) def initialize(opthash, &block)
@ -149,12 +148,12 @@ module WebGui
@block = block @block = block
end end
def render() def render
html="" html = ''
boxes.each do |value, text| boxes.each do |value, text|
html+="<input type=\"checkbox\" id=#{id} name=\"#{id}\" value=\"#{value.to_s}\">#{text}<br>" html += "<input type=\"checkbox\" id=#{id} name=\"#{id}\" value=\"#{value}\">#{text}<br>"
end end
return html html
end end
def on_checkbox_updated(event) def on_checkbox_updated(event)

View File

@ -1,26 +1,25 @@
# frozen_string_literal: true
$forcenochrome = false $forcenochrome = false
$stdout.sync = true $stdout.sync = true
$eventmanager = EventManager.new $eventmanager = EventManager.new
class WebGui::App module WebGui
class App
attr_reader :windows attr_reader :windows
def initialize(title, &block) def initialize(title, &block)
$app = self $app = self
if ARGV.length>0 $forcenochrome = true if ARGV.length.positive? && (ARGV[0] == '--forcenochrome')
if ARGV[0]=="--forcenochrome"
$forcenochrome=true
end
end
@windows = {} @windows = {}
@windows[:main] = WebGui::Window.new(name: :main, title: title) @windows[:main] = WebGui::Window.new(name: :main, title: title)
@windows[:main].instance_eval(&block) @windows[:main].instance_eval(&block)
end end
def render_document(wname) def render_document(wname)
window = windows[wname] window = windows[wname]
html = window.render html = window.render
css = window.render_css css = window.render_css
doc=<<-ENDDOC <<~ENDDOC
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -36,36 +35,36 @@ class WebGui::App
</body> </body>
</html> </html>
ENDDOC ENDDOC
return doc
end end
def add_window(wname, title, &block) def add_window(wname, title, &block)
@windows[wname] = WebGui::Window.new(name: wname, title: title) @windows[wname] = WebGui::Window.new(name: wname, title: title)
@windows[wname].instance_eval(&block) @windows[wname].instance_eval(&block)
end end
def run()
def run
servthread = Thread.new do servthread = Thread.new do
WebGui::Server.server(self) WebGui::Server.server(self)
end end
startwsserv() startwsserv
if File.exists? "/Applications/Google\ Chrome.app" and !$forcenochrome if File.exist?("/Applications/Google\ Chrome.app") && !$forcenochrome
`"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --app="http://localhost:2000"` `"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --app="http://localhost:2000"`
else else
puts "Chrome is not on your system." puts 'Chrome is not on your system.'
puts "Please install Chrome to use this framework properly." puts 'Please install Chrome to use this framework properly.'
puts "If Chrome is installed, please make sure it is called Google Chrome.app and is in the root Applications folder." puts 'If Chrome is installed, please make sure it is called Google Chrome.app and is in the root Applications folder.'
puts "The app will open in your default browser as a regular webpage instead." puts 'The app will open in your default browser as a regular webpage instead.'
sleep(5) sleep(5)
`open http://localhost:2000` `open http://localhost:2000`
servthread.join servthread.join
end end
end end
def startwsserv() def startwsserv
@serv = WebGui::WebSocketServer.new @serv = WebGui::WebSocketServer.new
Thread.new(self, @serv) do |parent, server| Thread.new(self, @serv) do |parent, server|
server.accept server.accept
while true loop do
message = server.recv message = server.recv
if message == false if message == false
server.accept server.accept
@ -78,19 +77,19 @@ class WebGui::App
def handlemessage(message) def handlemessage(message)
puts "Got message #{message}" puts "Got message #{message}"
part1,val=message.split("=") part1, val = message.split('=')
type=part1.match /[a-zA-Z]+/.to_s type = part1.match(/[a-zA-Z]+/.to_s)
type = type[0] type = type[0]
id=part1.match /\d+/ id = part1.match(/\d+/)
id = id[0].to_i id = id[0].to_i
if val if val
case type case type
when "menu" when 'menu'
val = val.to_sym val = val.to_sym
when "radiobutton" when 'radiobutton'
val = val.to_sym val = val.to_sym
when "checkbox" when 'checkbox'
vals=val.split("&") vals = val.split('&')
temp = [] temp = []
vals.each do |val| vals.each do |val|
temp.push(val.to_sym) temp.push(val.to_sym)
@ -108,3 +107,4 @@ class WebGui::App
@serv.send("#{type}#{id}=#{val}") @serv.send("#{type}#{id}=#{val}")
end end
end end
end

View File

@ -1,13 +1,17 @@
# frozen_string_literal: true
$id = 0 $id = 0
$idtoel = {} $idtoel = {}
def get_id() def get_id
id = $id id = $id
$id += 1 $id += 1
return id id
end end
class WebGui::Element < Dry::Struct
module WebGui
class Element < Dry::Struct
constructor_type :strict_with_defaults constructor_type :strict_with_defaults
attribute :id, Types::Coercible::Int.default { get_id() } attribute :id, Types::Coercible::Int.default { get_id }
def initialize(opthash = nil) def initialize(opthash = nil)
if opthash if opthash
super(opthash) super(opthash)
@ -15,10 +19,13 @@ class WebGui::Element < Dry::Struct
end end
$idtoel[id] = self $idtoel[id] = self
end end
def render()
return "" def render
''
end
def self.css
@ccs
end end
def self.css()
return @ccs
end end
end end

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
class EventManager class EventManager
include Dry::Events::Publisher[:event_manager] include Dry::Events::Publisher[:event_manager]
register_event("button.pushed") register_event('button.pushed')
register_event("menu.updated") register_event('menu.updated')
register_event("textfield.updated") register_event('textfield.updated')
register_event("checkbox.updated") register_event('checkbox.updated')
register_event("radiobutton.updated") register_event('radiobutton.updated')
end end

View File

@ -1,66 +1,75 @@
require "socket" # frozen_string_literal: true
class WebGui::Server
def self.sfile(file,headers,type,client,url) require 'socket'
module WebGui
class Server
def self.sfile(file, headers, type, client, _url)
total = file.length total = file.length
range=headers["Range"] range = headers['Range']
if range!=nil if !range.nil?
positions=range.split("=")[1].split("-") positions = range.split('=')[1].split('-')
start = positions[0].to_i(10) start = positions[0].to_i(10)
m_end=positions[1] ? positions[1].to_i(10) : total - 1; m_end = positions[1] ? positions[1].to_i(10) : total - 1
chunksize = (m_end - start) + 1 chunksize = (m_end - start) + 1
chunk = file[start, m_end + 1] chunk = file[start, m_end + 1]
if type=="mp4" case type
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"video/mp4"} when 'mp4'
elsif type=="webm" r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"video/webm"} 'Content-Length' => chunksize, 'Content-Type' => 'video/mp4' }
elsif type=="mpeg" when 'webm'
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"audio/mpeg"} r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
elsif type=="ogg" 'Content-Length' => chunksize, 'Content-Type' => 'video/webm' }
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"audio/ogg"} when 'mpeg'
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
'Content-Length' => chunksize, 'Content-Type' => 'audio/mpeg' }
when 'ogg'
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
'Content-Length' => chunksize, 'Content-Type' => 'audio/ogg' }
end end
header="" header = ''
r_headers.each do |key, value| r_headers.each do |key, value|
header += "#{key}: #{value}\n" header += "#{key}: #{value}\n"
end end
client.puts "HTTP/1.1 206 Partial Content" client.puts 'HTTP/1.1 206 Partial Content'
client.print "#{header}" client.print header.to_s
client.print "\n" client.print "\n"
client.print "#{chunk}" client.print chunk.to_s
else else
if type=="mp4" case type
r_headers={"Content-Type"=>"video/mp4"} when 'mp4'
elsif type=="webm" r_headers = { 'Content-Type' => 'video/mp4' }
r_headers={"Content-Type"=>"video/webm"} when 'webm'
elsif type=="mpeg" r_headers = { 'Content-Type' => 'video/webm' }
r_headers={"Content-Type"=>"audio/mpeg"} when 'mpeg'
elsif type=="ogg" r_headers = { 'Content-Type' => 'audio/mpeg' }
r_headers={"Content-Type"=>"audio/ogg"} when 'ogg'
r_headers = { 'Content-Type' => 'audio/ogg' }
end end
header="" header = ''
r_headers.each do |key, value| r_headers.each do |key, value|
header += "#{key}: #{value}\n" header += "#{key}: #{value}\n"
end end
client.puts "HTTP/1.1 200 OK" client.puts 'HTTP/1.1 200 OK'
client.print "#{header}" client.print header.to_s
client.print "\n" client.print "\n"
client.print "#{file}" client.print file.to_s
end end
client.close client.close
end end
def self.server(app) def self.server(app)
Thread::abort_on_exception=true Thread.abort_on_exception = true
server = TCPServer.new(2000) server = TCPServer.new(2000)
$debug = true $debug = true
igfiles=["favicon.ico"] igfiles = ['favicon.ico']
loop do loop do
Thread.start(server.accept) do |client| Thread.start(server.accept) do |client|
begin begin
lines = [] lines = []
line = client.gets line = client.gets
while line != "\r\n" while line != "\r\n"
if line==nil if line.nil?
lines=[""] lines = ['']
runreq = false runreq = false
break break
end end
@ -70,63 +79,61 @@ class WebGui::Server
i = 0 i = 0
lines.each do |value| lines.each do |value|
lines[i] = value.chomp lines[i] = value.chomp
i=i+1 i += 1
end end
temp = lines.shift temp = lines.shift
method=temp.split(" ")[0] method = temp.split(' ')[0]
url=temp.split(" ")[1] url = temp.split(' ')[1]
headers = {} headers = {}
lines.each do |value| lines.each do |value|
temp=value.split(": ") temp = value.split(': ')
headers[temp[0]] = temp[1] headers[temp[0]] = temp[1]
end end
wname=url.gsub("/","") wname = url.gsub('/', '')
if igfiles.include? wname if igfiles.include? wname
client.puts "HTTP/1.1 404 Not Found" client.puts 'HTTP/1.1 404 Not Found'
client.puts "Content-Type:text/html" client.puts 'Content-Type:text/html'
client.puts client.puts
client.print "" client.print ''
else elsif wname.include? '.jpg'
if wname.include? ".jpg"
img = File.read(wname) img = File.read(wname)
client.puts "HTTP/1.1 200 OK" client.puts 'HTTP/1.1 200 OK'
client.puts "Content-Type:image/jpeg" client.puts 'Content-Type:image/jpeg'
client.puts client.puts
client.print img client.print img
elsif wname.include? ".mp4" elsif wname.include? '.mp4'
sfile(File.open(wname, "rb") {|io| io.read},headers,"mp4",client,url) sfile(File.open(wname, 'rb', &:read), headers, 'mp4', client, url)
elsif wname.include? ".webm" elsif wname.include? '.webm'
sfile(File.open(wname, "rb") {|io| io.read},headers,"webm",client,url) sfile(File.open(wname, 'rb', &:read), headers, 'webm', client, url)
elsif wname.include? ".js" elsif wname.include? '.js'
if wname=="main.js" if wname == 'main.js'
jspath=`gem which web_gui`.split("/") jspath = `gem which web_gui`.split('/')
jspath.pop jspath.pop
jspath.push "main.js" jspath.push 'main.js'
jspath=jspath.join("/") jspath = jspath.join('/')
script = File.read(jspath) script = File.read(jspath)
else else
script = File.read(wname) script = File.read(wname)
end end
client.puts "HTTP/1.1 200 OK" client.puts 'HTTP/1.1 200 OK'
client.puts "Content-Type:application/javascript" client.puts 'Content-Type:application/javascript'
client.puts client.puts
client.print script client.print script
else else
wname="main" if wname=="" wname = 'main' if wname == ''
wname = wname.to_sym wname = wname.to_sym
doc = app.render_document(wname) doc = app.render_document(wname)
client.puts "HTTP/1.1 200 OK" client.puts 'HTTP/1.1 200 OK'
client.puts "Content-Type:text/html" client.puts 'Content-Type:text/html'
client.puts client.puts
client.print doc client.print doc
end end
end
client.close client.close
rescue StandardError => e rescue StandardError => e
puts e puts e
puts e.backtrace puts e.backtrace
client.puts "HTTP/1.1 500 Internal Server Error" client.puts 'HTTP/1.1 500 Internal Server Error'
client.puts "Content-Type:text/plain" client.puts 'Content-Type:text/plain'
client.puts client.puts
client.puts "Server error: #{e}" client.puts "Server error: #{e}"
client.print e.backtrace.join("\n") client.print e.backtrace.join("\n")
@ -136,3 +143,4 @@ class WebGui::Server
end end
end end
end end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module WebGui module WebGui
VERSION = "1.0.0" VERSION = '1.0.0'
end end

View File

@ -1,5 +1,8 @@
class WebGui::Window < Dry::Struct # frozen_string_literal: true
attribute :title, Types::Coercible::String.default("")
module WebGui
class Window < Dry::Struct
attribute :title, Types::Coercible::String.default('')
attribute :name, Types::Coercible::String attribute :name, Types::Coercible::String
def initialize(opthash) def initialize(opthash)
super(opthash) super(opthash)
@ -7,28 +10,28 @@ class WebGui::Window < Dry::Struct
end end
def add_element(element) def add_element(element)
raise ArgumentError, "The element must be of type Element." unless element.is_a? WebGui::Element raise ArgumentError, 'The element must be of type Element.' unless element.is_a? WebGui::Element
@elements.push element @elements.push element
$eventmanager.subscribe(element) $eventmanager.subscribe(element)
return element element
end end
def render() def render
html="" html = ''
@elements.each do |el| @elements.each do |el|
html+="<p>#{el.render()}</p>" html += "<p>#{el.render}</p>"
end end
return html html
end end
def render_css() def render_css
css="" css = ''
WebGui::Element.descendants.each do |el| WebGui::Element.descendants.each do |el|
elcss = el.css elcss = el.css
if elcss!=nil css += elcss unless elcss.nil?
css+=elcss end
css
end end
end end
return css
end
end end

View File

@ -1,18 +1,20 @@
# frozen_string_literal: true
require 'socket' # Provides TCPServer and TCPSocket classes require 'socket' # Provides TCPServer and TCPSocket classes
require 'digest/sha1' require 'digest/sha1'
class WebGui::WebSocketServer module WebGui
def initialize(host="localhost",port=2345) class WebSocketServer
def initialize(host = 'localhost', port = 2345)
@server = TCPServer.new(host, port) @server = TCPServer.new(host, port)
end end
def accept() def accept
# Wait for a connection # Wait for a connection
@socket = @server.accept @socket = @server.accept
# Read the HTTP request. We know it's finished when we see a line with nothing but \r\n # Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
http_request = "" http_request = ''
while (line = @socket.gets) && (line != "\r\n") while (line = @socket.gets) && (line != "\r\n")
http_request += line http_request += line
end end
@ -24,49 +26,52 @@ class WebGui::WebSocketServer
socket.close socket.close
return return
end end
response_key = Digest::SHA1.base64digest([websocket_key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"].join) response_key = Digest::SHA1.base64digest([websocket_key, '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'].join)
@socket.write <<-eos @socket.write <<~EOS
HTTP/1.1 101 Switching Protocols HTTP/1.1 101 Switching Protocols
Upgrade: websocket Upgrade: websocket
Connection: Upgrade Connection: Upgrade
Sec-WebSocket-Accept: #{response_key} Sec-WebSocket-Accept: #{response_key}
#{' '}
eos EOS
end end
def recv() def recv
first_byte = @socket.getbyte first_byte = @socket.getbyte
fin = first_byte & 0b10000000 fin = first_byte & 0b10000000
opcode = first_byte & 0b00001111 opcode = first_byte & 0b00001111
fin = fin >> 7 fin = fin >> 7
fin = (fin ? true : false) fin = (fin ? true : false)
raise "We don't support continuations" unless fin raise "We don't support continuations" unless fin
unless opcode == 1 or opcode == 8
raise "We only support text data and close frame" unless (opcode == 1) || (opcode == 8)
send("",3) raise 'We only support text data and close frame'
send('', 3)
end end
if opcode == 1 case opcode
when 1
second_byte = @socket.getbyte second_byte = @socket.getbyte
is_masked = second_byte & 0b10000000 is_masked = second_byte & 0b10000000
payload_size = second_byte & 0b01111111 payload_size = second_byte & 0b01111111
raise "All incoming frames should be masked according to the websocket spec" unless is_masked raise 'All incoming frames should be masked according to the websocket spec' unless is_masked
raise "We only support payloads < 126 bytes in length" unless payload_size < 126 raise 'We only support payloads < 126 bytes in length' unless payload_size < 126
mask = 4.times.map { @socket.getbyte } mask = 4.times.map { @socket.getbyte }
data = payload_size.times.map { @socket.getbyte } data = payload_size.times.map { @socket.getbyte }
unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] } unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
string=unmasked_data.pack('C*').force_encoding('utf-8') unmasked_data.pack('C*').force_encoding('utf-8')
return string
elsif opcode == 8 when 8
send("",0) send('', 0)
return false false
end end
end end
def send(message, closetype = false) def send(message, closetype = false)
if closetype if closetype
output = [0x88, 2, 3, 0xe8 + closetype] output = [0x88, 2, 3, 0xe8 + closetype]
@socket.write output.pack("C*") @socket.write output.pack('C*')
@socket.close @socket.close
else else
output = [0x81, message.size, message] output = [0x81, message.size, message]
@ -74,3 +79,4 @@ Sec-WebSocket-Accept: #{response_key}
end end
end end
end end
end

View File

@ -1,17 +1,18 @@
# coding: utf-8 # frozen_string_literal: true
lib = File.expand_path("../lib", __FILE__)
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "web_gui/version" require 'web_gui/version'
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name="web_gui" spec.name = 'web_gui'
spec.version = WebGui::VERSION spec.version = WebGui::VERSION
spec.authors=["pjht"] spec.authors = ['pjht']
spec.email=["pjht@users.noreply.github.com"] spec.email = ['pjht@users.noreply.github.com']
spec.summary= "A GUI framework for ruby based around the web browser" spec.summary = 'A GUI framework for ruby based around the web browser'
spec.homepage="https://github.com/pjht/web_gui" spec.homepage = 'https://github.com/pjht/web_gui'
spec.files=["lib/web_gui.rb","lib/main.js"]+Dir.glob("lib/web_gui/*.rb") spec.files = ['lib/web_gui.rb', 'lib/main.js'] + Dir.glob('lib/web_gui/*.rb')
spec.bindir="exe" spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths=["lib"] spec.require_paths = ['lib']
end end