2 Commits

6 changed files with 151 additions and 87 deletions

View File

@@ -10,7 +10,6 @@ gem "digest-crc"
gem "ircparser" gem "ircparser"
gem "rexml" gem "rexml"
gem "rubyzip" gem "rubyzip"
gem "websocket-client-simple"
gem "win32-process", platforms: [:windows] gem "win32-process", platforms: [:windows]
gem "win32-security", platforms: [:windows] gem "win32-security", platforms: [:windows]

View File

@@ -2,6 +2,9 @@ class W3DHub
class Api class Api
class ServerListUpdater class ServerListUpdater
LOG_TAG = "W3DHub::Api::ServerListUpdater".freeze LOG_TAG = "W3DHub::Api::ServerListUpdater".freeze
TYPE_PING = 6
include CyberarmEngine::Common include CyberarmEngine::Common
@@instance = nil @@instance = nil
@@ -15,6 +18,7 @@ class W3DHub
def initialize def initialize
@auto_reconnect = false @auto_reconnect = false
@reconnection_delay = 1
@invocation_id = 0 @invocation_id = 0
@@ -23,16 +27,14 @@ class W3DHub
end end
def run def run
return
Thread.new do Thread.new do
Sync do |task| Sync do |task|
begin begin
async_connect(task) @auto_reconnect = true
while W3DHub::BackgroundWorker.alive? while W3DHub::BackgroundWorker.alive?
async_connect(task) if @auto_reconnect connect if @auto_reconnect
sleep 1 sleep @reconnection_delay
end end
rescue => e rescue => e
puts e puts e
@@ -48,39 +50,6 @@ class W3DHub
@@instance = nil @@instance = nil
end end
def async_connect(task)
@auto_reconnect = false
logger.debug(LOG_TAG) { "Requesting connection token..." }
response = Api.post("/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, "", :gsh)
if response.status != 200
@auto_reconnect = true
return
end
data = JSON.parse(response.body, symbolize_names: true)
@invocation_id = 0 if @invocation_id > 9095
id = data[:connectionToken]
endpoint = "#{Api::SERVER_LIST_ENDPOINT}/listings/push/v2?id=#{id}"
logger.debug(LOG_TAG) { "Connecting to websocket..." }
Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse(endpoint)) do |connection|
logger.debug(LOG_TAG) { "Requesting json protocol, v1..." }
async_websocket_send(connection, { protocol: "json", version: 1 }.to_json)
end
end
def async_websocket_send(connection, payload)
connection.write("#{payload}\x1e")
connection.flush
end
def async_websocket_read(connection, payload)
end
def connect def connect
@auto_reconnect = false @auto_reconnect = false
@@ -89,9 +58,13 @@ class W3DHub
if response.status != 200 if response.status != 200
@auto_reconnect = true @auto_reconnect = true
@reconnection_delay = @reconnection_delay * 2
@reconnection_delay = 60 if @reconnection_delay > 60
return return
end end
@reconnection_delay = 1
data = JSON.parse(response.body, symbolize_names: true) data = JSON.parse(response.body, symbolize_names: true)
@invocation_id = 0 if @invocation_id > 9095 @invocation_id = 0 if @invocation_id > 9095
@@ -100,7 +73,7 @@ class W3DHub
logger.debug(LOG_TAG) { "Connecting to websocket..." } logger.debug(LOG_TAG) { "Connecting to websocket..." }
this = self this = self
@ws = WebSocket::Client::Simple.connect(endpoint, headers: Api::DEFAULT_HEADERS) do |ws| @ws = WebSocketClient.new.connect(endpoint, headers: Api::DEFAULT_HEADERS) do |ws|
ws.on(:open) do ws.on(:open) do
logger.debug(LOG_TAG) { "Requesting json protocol, v1..." } logger.debug(LOG_TAG) { "Requesting json protocol, v1..." }
ws.send({ protocol: "json", version: 1 }.to_json + "\x1e") ws.send({ protocol: "json", version: 1 }.to_json + "\x1e")
@@ -115,65 +88,71 @@ class W3DHub
end end
ws.on(:message) do |msg| ws.on(:message) do |msg|
msg = msg.data.split("\x1e").first msg = msg.to_str.split("\x1e").first
hash = JSON.parse(msg, symbolize_names: true) hash = JSON.parse(msg, symbolize_names: true)
# pp hash if hash[:target] != "ServerStatusChanged" && hash[:type] != 6 && hash[:type] != 3 # pp hash if hash[:target] != "ServerStatusChanged" && hash[:type] != 6 && hash[:type] != 3
# Send PING(?) # Send PING(?)
if hash.empty? || hash[:type] == 6 if hash.empty? || hash[:type] == TYPE_PING
ws.send({ type: 6 }.to_json + "\x1e") ws.send({ type: TYPE_PING }.to_json + "\x1e")
else next
case hash[:type] end
when 1
case hash[:target]
when "ServerRegistered"
data = hash[:arguments].first
this.invocation_id += 1 case hash[:type]
out = { "type": 1, "invocationId": "#{this.invocation_id}", "target": "SubscribeToServerStatusUpdates", "arguments": [data[:id], 1] } when 1
ws.send(out.to_json + "\x1e") case hash[:target]
when "ServerRegistered"
data = hash[:arguments].first
BackgroundWorker.foreground_job( this.invocation_id += 1
->(data) { [Api.server_details(data[:id], 2), data] }, out = {
->(array) do "type": 1,
server_data, data = array "invocationId": "#{this.invocation_id}",
"target": "SubscribeToServerStatusUpdates",
"arguments": [data[:id], 1]
}
ws.send(out.to_json + "\x1e")
next unless server_data BackgroundWorker.foreground_job(
->(data) { [Api.server_details(data[:id], 2), data] },
->(array) do
server_data, data = array
data[:status] = server_data next unless server_data
server = ServerListServer.new(data) data[:status] = server_data
Store.server_list.push(server)
States::Interface.instance&.update_server_browser(server, :update)
end,
nil,
data
)
when "ServerStatusChanged" server = ServerListServer.new(data)
id, data = hash[:arguments] Store.server_list.push(server)
server = Store.server_list.find { |s| s.id == id } States::Interface.instance&.update_server_browser(server, :update)
server_updated = server&.update(data) end,
nil,
data
)
BackgroundWorker.foreground_job(->(server) { server }, ->(server) { States::Interface.instance&.update_server_browser(server, :update) }, nil, server) if server_updated when "ServerStatusChanged"
id, data = hash[:arguments]
server = Store.server_list.find { |s| s.id == id }
server_updated = server&.update(data)
when "ServerUnregistered" BackgroundWorker.foreground_job(->(server) { server }, ->(server) { States::Interface.instance&.update_server_browser(server, :update) }, nil, server) if server_updated
id = hash[:arguments].first
server = Store.server_list.find { |s| s.id == id }
if server when "ServerUnregistered"
Store.server_list.delete(server) id = hash[:arguments].first
BackgroundWorker.foreground_job(->(server) { server }, ->(server) { States::Interface.instance&.update_server_browser(server, :remove) }, nil, server) server = Store.server_list.find { |s| s.id == id }
end
if server
Store.server_list.delete(server)
BackgroundWorker.foreground_job(->(server) { server }, ->(server) { States::Interface.instance&.update_server_browser(server, :remove) }, nil, server)
end end
end end
end end
end end
ws.on(:close) do |e| ws.on(:close) do
logger.error(LOG_TAG) { e } logger.error(LOG_TAG) { "Connection closed." }
this.auto_reconnect = true this.auto_reconnect = true
ws.close ws.close
end end
@@ -218,14 +197,24 @@ class W3DHub
# unsubscribe from removed servers # unsubscribe from removed servers
removed_servers.each do removed_servers.each do
@invocation_id += 1 @invocation_id += 1
out = { "type": 1, "invocationId": "#{@invocation_id}", "target": "SubscribeToServerStatusUpdates", "arguments": [server.id, 0] } out = {
"type": 1,
"invocationId": "#{@invocation_id}",
"target": "SubscribeToServerStatusUpdates",
"arguments": [server.id, 0]
}
ws.send(out.to_json + "\x1e") ws.send(out.to_json + "\x1e")
end end
# subscribe to new servers # subscribe to new servers
new_servers.each do new_servers.each do
@invocation_id += 1 @invocation_id += 1
out = { "type": 1, "invocationId": "#{@invocation_id}", "target": "SubscribeToServerStatusUpdates", "arguments": [server.id, 1] } out = {
"type": 1,
"invocationId": "#{@invocation_id}",
"target": "SubscribeToServerStatusUpdates",
"arguments": [server.id, 1]
}
ws.send(out.to_json + "\x1e") ws.send(out.to_json + "\x1e")
end end
end end

View File

@@ -249,9 +249,17 @@ class W3DHub
return nil unless app_data return nil unless app_data
found_server = Store.server_list.select do |server| server_options = Store.server_list.select do |server|
server.game == app_id && server.channel == channel && !server.status.password && server.status.player_count < server.status.max_players server.game == app_id &&
end&.first server.channel == channel &&
!server.status.password &&
server.status.player_count < server.status.max_players
end
# try to find server with lowest ping and matching version
found_server = server_options.find { |server| server.version == app_data[:installed_version] }
# try to find server with lowest ping and undefined version
found_server ||= server_options.find { |server| server.version == Api::ServerListServer::NO_OR_DEFAULT_VERSION }
found_server ? found_server : nil found_server ? found_server : nil
end end
@@ -283,7 +291,7 @@ class W3DHub
end end
end end
def favorive(app_id, bool) def favorite(app_id, bool)
Store.settings[:favorites] ||= {} Store.settings[:favorites] ||= {}
if bool if bool

View File

@@ -369,7 +369,7 @@ class W3DHub
flow(width: 1.0, height: 28, padding: 8) do flow(width: 1.0, height: 28, padding: 8) do
para "Favorite", fill: true para "Favorite", fill: true
toggle_button checked: Store.application_manager.favorite?(game.id), height: 18, padding_top: 3, padding_right: 3, padding_bottom: 3, padding_left: 3 do |btn| toggle_button checked: Store.application_manager.favorite?(game.id), height: 18, padding_top: 3, padding_right: 3, padding_bottom: 3, padding_left: 3 do |btn|
Store.application_manager.favorive(game.id, btn.value) Store.application_manager.favorite(game.id, btn.value)
Store.settings.save_settings Store.settings.save_settings
populate_games_list populate_games_list

68
lib/websocket_client.rb Normal file
View File

@@ -0,0 +1,68 @@
class W3DHub
class WebSocketClient
def initialize
@errored = nil
@connection = nil
@events = {
open: nil,
message: nil,
close: nil,
error: nil
}
end
def connect(endpoint, headers: nil, &block)
yield(self)
Sync do |task|
endpoint = Async::HTTP::Endpoint.parse(endpoint, alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
Async::WebSocket::Client.connect(endpoint, headers: headers) do |connection|
@connection = connection
@events[:open]&.call
while message = connection.read
@events[:message].call(message)
end
# FIXME: Don't rescue for all ta errors?
rescue => error
@errored = true
@events[:error]&.call(error)
ensure
@events[:close]&.call unless @errored
@connection = nil
@errored = false
end
end
self
end
def on(event, &block)
raise "Event must be a symbol" unless event.is_a?(Symbol)
raise "Unknown event: #{event.inspect}" unless @events.keys.include?(event)
raise "No block given for #{event.inspect}" unless block_given?
@events[event] = block
end
def send(data, type: :text)
@connection&.write(data)
@connection&.flush
end
def close
@connection&.close
end
def open?
!closed?
end
def closed?
@connection&.closed?
end
end
end

View File

@@ -87,7 +87,6 @@ class W3DHub
BLACK_IMAGE = Gosu::Image.from_blob(1, 1, "\x00\x00\x00\xff") BLACK_IMAGE = Gosu::Image.from_blob(1, 1, "\x00\x00\x00\xff")
end end
require "websocket-client-simple"
require "English" require "English"
require "sdl2" require "sdl2"
@@ -110,6 +109,7 @@ require_relative "lib/ico"
require_relative "lib/broadcast_server" require_relative "lib/broadcast_server"
require_relative "lib/hardware_survey" require_relative "lib/hardware_survey"
require_relative "lib/game_settings" require_relative "lib/game_settings"
require_relative "lib/websocket_client"
require_relative "lib/background_worker" require_relative "lib/background_worker"
require_relative "lib/application_manager" require_relative "lib/application_manager"
require_relative "lib/application_manager/manifest" require_relative "lib/application_manager/manifest"