Changed how account data is stored, authorization header is now sent when logged in, adjusted spacing game filters for server list, fixed crashing when trying to load icon for game whos icon is not present, a bit of code cleanup to Api to use Async::HTTP::Client over Async::Internet directly (for everything except get requests), probably a few misc. changes

This commit is contained in:
2022-02-04 08:59:15 -06:00
parent 4996315aeb
commit 7da254fd61
11 changed files with 114 additions and 74 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
*.json *.json
data/cache/* data/cache/*
!data/cache/.gitkeep !data/cache/.gitkeep
_*.* _*.*
*.log

View File

@@ -13,6 +13,20 @@ class W3DHub
ENDPOINT = "https://secure.w3dhub.com".freeze ENDPOINT = "https://secure.w3dhub.com".freeze
def self.post(url, headers = DEFAULT_HEADERS, body = nil)
@client ||= Async::HTTP::Client.new(Async::HTTP::Endpoint.parse(ENDPOINT, protocol: Async::HTTP::Protocol::HTTP10))
# TODO: Check if session has expired and attempt to refresh session before submitting request
# Inject Authorization header if account data is populated
if Store.account
headers = headers.dup
headers << ["Authorization", "Bearer #{Store.account.access_token}"]
end
@client.post(url, headers, body)
end
# Method: POST # Method: POST
# FORMAT: JSON # FORMAT: JSON
@@ -28,9 +42,9 @@ class W3DHub
# #
# On a failed login the service responds with: # On a failed login the service responds with:
# {"error":"login-failed"} # {"error":"login-failed"}
def self.refresh_user_login(internet, refresh_token) def self.refresh_user_login(refresh_token)
body = "data=#{JSON.dump({refreshToken: refresh_token})}" body = "data=#{JSON.dump({refreshToken: refresh_token})}"
response = internet.post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) response = post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body)
if response.success?#status == 200 if response.success?#status == 200
user_data = JSON.parse(response.read, symbolize_names: true) user_data = JSON.parse(response.read, symbolize_names: true)
@@ -38,7 +52,7 @@ class W3DHub
return false if user_data[:error] return false if user_data[:error]
body = "data=#{JSON.dump({ id: user_data[:userid] })}" body = "data=#{JSON.dump({ id: user_data[:userid] })}"
user_details = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) user_details = post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body)
if user_details.success? if user_details.success?
user_details_data = JSON.parse(user_details.read, symbolize_names: true) user_details_data = JSON.parse(user_details.read, symbolize_names: true)
@@ -51,9 +65,9 @@ class W3DHub
end end
# See #user_refresh_token # See #user_refresh_token
def self.user_login(internet, username, password) def self.user_login(username, password)
body = "data=#{JSON.dump({username: username, password: password})}" body = "data=#{JSON.dump({username: username, password: password})}"
response = internet.post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) response = post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body)
if response.success? if response.success?
user_data = JSON.parse(response.read, symbolize_names: true) user_data = JSON.parse(response.read, symbolize_names: true)
@@ -61,7 +75,7 @@ class W3DHub
return false if user_data[:error] return false if user_data[:error]
body = "data=#{JSON.dump({ id: user_data[:userid] })}" body = "data=#{JSON.dump({ id: user_data[:userid] })}"
user_details = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) user_details = post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body)
if user_details.success? if user_details.success?
user_details_data = JSON.parse(user_details.read, symbolize_names: true) user_details_data = JSON.parse(user_details.read, symbolize_names: true)
@@ -77,14 +91,14 @@ class W3DHub
# Client sends an Authorization header bearer token which is received from logging in (Required?) # Client sends an Authorization header bearer token which is received from logging in (Required?)
# #
# Response: avatar-uri (Image download uri), id, username # Response: avatar-uri (Image download uri), id, username
def self.user_details(internetn, id) def self.user_details(id)
end end
# /apis/w3dhub/1/get-service-status # /apis/w3dhub/1/get-service-status
# Service response: # Service response:
# {"services":{"authentication":true,"packageDownload":true}} # {"services":{"authentication":true,"packageDownload":true}}
def self.service_status(internet) def self.service_status
response = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS) response = post("#{ENDPOINT}/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS)
if response.success? if response.success?
ServiceStatus.new(response.read) ServiceStatus.new(response.read)
@@ -97,8 +111,8 @@ class W3DHub
# Client sends an Authorization header bearer token which is received from logging in (Optional) # Client sends an Authorization header bearer token which is received from logging in (Optional)
# Launcher sends an empty data request: data={} # Launcher sends an empty data request: data={}
# Response is a list of applications/games # Response is a list of applications/games
def self.applications(internet) def self.applications
response = internet.post("#{ENDPOINT}/apis/launcher/1/get-applications", DEFAULT_HEADERS) response = post("#{ENDPOINT}/apis/launcher/1/get-applications")
if response.success? if response.success?
Applications.new(response.read) Applications.new(response.read)
@@ -111,11 +125,11 @@ class W3DHub
# Client sends an Authorization header bearer token which is received from logging in (Optional) # Client sends an Authorization header bearer token which is received from logging in (Optional)
# Client requests news for a specific application/game e.g.: data={"category":"ia"} ("launcher-home" retrieves the weekly hub updates) # Client requests news for a specific application/game e.g.: data={"category":"ia"} ("launcher-home" retrieves the weekly hub updates)
# Response is a JSON hash with a "highlighted" and "news" keys; the "news" one seems to be the desired one # Response is a JSON hash with a "highlighted" and "news" keys; the "news" one seems to be the desired one
def self.news(internet, category) def self.news(category)
body = "data=#{JSON.dump({category: category})}" body = "data=#{JSON.dump({category: category})}"
response = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body) response = post("#{ENDPOINT}/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body)
if response.success?#status == 200 if response.success?
News.new(response.read) News.new(response.read)
else else
false false
@@ -126,11 +140,9 @@ class W3DHub
# /apis/launcher/1/get-package-details # /apis/launcher/1/get-package-details
# client requests package details: data={"packages":[{"category":"games","name":"apb.ico","subcategory":"apb","version":""}]} # client requests package details: data={"packages":[{"category":"games","name":"apb.ico","subcategory":"apb","version":""}]}
def self.package_details(internet, packages) def self.package_details(packages)
body = URI.encode_www_form("data": JSON.dump({ packages: packages })) body = URI.encode_www_form("data": JSON.dump({ packages: packages }))
endpoint = Async::HTTP::Endpoint.parse("#{ENDPOINT}/apis/launcher/1/get-package-details", protocol: Async::HTTP::Protocol::HTTP10) response = post("#{ENDPOINT}/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body)
client = Async::HTTP::Client.new(endpoint)
response = client.post("#{ENDPOINT}/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body)
if response.success? if response.success?
hash = JSON.parse(response.read, symbolize_names: true) hash = JSON.parse(response.read, symbolize_names: true)
@@ -144,8 +156,8 @@ class W3DHub
# client requests package: data={"category":"games","name":"ECW_Asteroids.zip","subcategory":"ecw","version":"1.0.0.0"} # client requests package: data={"category":"games","name":"ECW_Asteroids.zip","subcategory":"ecw","version":"1.0.0.0"}
# #
# server responds with download bytes, probably supports chunked download and resume # server responds with download bytes, probably supports chunked download and resume
def self.package(internet, package, &block) def self.package(package, &block)
Cache.fetch_package(internet, package, block) Cache.fetch_package(package, block)
end end
#! === Server List API === !# #! === Server List API === !#

View File

@@ -11,7 +11,7 @@ class W3DHub
@username = @data[:username] @username = @data[:username]
@displayname = @data[:displayname] @displayname = @data[:displayname]
@avatar_uri = user_details[:"avatar-uri"] @avatar_uri = user_details[:"avatar-uri"] || @data[:avatar_uri]
@user_level = @data[:userlevel] @user_level = @data[:userlevel]
@session_token = @data[:"session-token"] @session_token = @data[:"session-token"]
@@ -21,6 +21,14 @@ class W3DHub
@studio_user_level = @data[:"studio-userlevel"] # Dunno? @studio_user_level = @data[:"studio-userlevel"] # Dunno?
end end
def to_json(env)
d = @data.dup
d[:avatar_uri] = @avatar_uri
d[:access_token_expiry] = d[:access_token_expiry].to_i
d.to_json(env)
end
end end
end end
end end

View File

@@ -325,8 +325,7 @@ class W3DHub
} }
end end
internet = Async::HTTP::Internet.instance package_details = Api.package_details(hashes)
package_details = Api.package_details(internet, hashes)
if package_details if package_details
@packages = [package_details].flatten @packages = [package_details].flatten
@@ -515,10 +514,9 @@ class W3DHub
def fetch_manifest(category, subcategory, name, version, &block) def fetch_manifest(category, subcategory, name, version, &block)
# Check for and integrity of local manifest # Check for and integrity of local manifest
internet = Async::HTTP::Internet.instance
package = nil package = nil
array = Api.package_details(internet, [{ category: category, subcategory: subcategory, name: name, version: version }]) array = Api.package_details([{ category: category, subcategory: subcategory, name: name, version: version }])
if array.is_a?(Array) if array.is_a?(Array)
package = array.first package = array.first
else else
@@ -543,7 +541,7 @@ class W3DHub
internet = Async::HTTP::Internet.instance internet = Async::HTTP::Internet.instance
Api.package(internet, package) do |chunk, remaining_bytes, total_bytes| Api.package(package) do |chunk, remaining_bytes, total_bytes|
block&.call(chunk, remaining_bytes, total_bytes) block&.call(chunk, remaining_bytes, total_bytes)
end end
end end

View File

@@ -7,12 +7,14 @@ class W3DHub
end end
# Fetch a generic uri # Fetch a generic uri
def self.fetch(internet, uri, force_fetch = false) def self.fetch(uri, force_fetch = false)
path = path(uri) path = path(uri)
if !force_fetch && File.exist?(path) if !force_fetch && File.exist?(path)
path path
else else
internet = Async::HTTP::Internet.instance
response = internet.get(uri, W3DHub::Api::DEFAULT_HEADERS) response = internet.get(uri, W3DHub::Api::DEFAULT_HEADERS)
if response.success? if response.success?
@@ -46,9 +48,10 @@ class W3DHub
end end
# Download a W3D Hub package # Download a W3D Hub package
def self.fetch_package(internet, package, block) def self.fetch_package(package, block)
path = package_path(package.category, package.subcategory, package.name, package.version) path = package_path(package.category, package.subcategory, package.name, package.version)
headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": Api::USER_AGENT } headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": Api::USER_AGENT }
headers["Authorization"] = "Bearer #{Store.account.access_token}" if Store.account
start_from_bytes = package.custom_partially_valid_at_bytes start_from_bytes = package.custom_partially_valid_at_bytes
puts " Start from bytes: #{start_from_bytes} of #{package.size}" puts " Start from bytes: #{start_from_bytes} of #{package.size}"

View File

@@ -63,18 +63,18 @@ class W3DHub
Async do Async do
internet = Async::HTTP::Internet.instance internet = Async::HTTP::Internet.instance
fetch_w3dhub_news(internet) fetch_w3dhub_news
populate_w3dhub_news populate_w3dhub_news
end end
end end
end end
def fetch_w3dhub_news(internet) def fetch_w3dhub_news
news = Api.news(internet, "launcher-home") news = Api.news("launcher-home")
if news if news
news.items[0..9].each do |item| news.items[0..9].each do |item|
Cache.fetch(internet, item.image) Cache.fetch(item.image)
end end
@w3dhub_news = news @w3dhub_news = news

View File

@@ -28,7 +28,8 @@ class W3DHub
background task.application.color background task.application.color
flow(width: 0.70, height: 1.0) do flow(width: 0.70, height: 1.0) do
@application_image = image "#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png", height: 1.0 image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/app.png"
@application_image = image image_path, height: 1.0
stack(margin_left: 8, width: 0.75) do stack(margin_left: 8, width: 0.75) do
@application_name_label = tagline "#{task.application.name}" @application_name_label = tagline "#{task.application.name}"

View File

@@ -4,7 +4,9 @@ class W3DHub
def setup def setup
@game_news ||= {} @game_news ||= {}
@focused_game ||= Store.applications.games.find { |g| g.id == Store.settings[:last_selected_app] } @focused_game ||= Store.applications.games.find { |g| g.id == Store.settings[:last_selected_app] }
@focused_game ||= Store.applications.games.find { |g| g.id == "ren" }
@focused_channel ||= @focused_game.channels.find { |c| c.id == Store.settings[:last_selected_channel] } @focused_channel ||= @focused_game.channels.find { |c| c.id == Store.settings[:last_selected_channel] }
@focused_channel ||= @focused_game.channels.first
body.clear do body.clear do
# Games List # Games List
@@ -38,7 +40,9 @@ class W3DHub
image "#{GAME_ROOT_PATH}/media/ui_icons/return.png", width: 1.0, color: Gosu::Color::GRAY if Store.application_manager.updateable?(game.id, game.channels.first.id) image "#{GAME_ROOT_PATH}/media/ui_icons/return.png", width: 1.0, color: Gosu::Color::GRAY if Store.application_manager.updateable?(game.id, game.channels.first.id)
image "#{GAME_ROOT_PATH}/media/ui_icons/import.png", width: 0.5, color: 0x88_ffffff unless Store.application_manager.installed?(game.id, game.channels.first.id) image "#{GAME_ROOT_PATH}/media/ui_icons/import.png", width: 0.5, color: 0x88_ffffff unless Store.application_manager.installed?(game.id, game.channels.first.id)
end end
image "#{GAME_ROOT_PATH}/media/icons/#{game.id}.png", height: 48, color: Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x88_ffffff image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{game.id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{game.id}.png" : "#{GAME_ROOT_PATH}/media/icons/app.png"
image image_path, height: 48, color: Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x88_ffffff
end end
inscription game.name, width: 1.0, text_align: :center inscription game.name, width: 1.0, text_align: :center
end end
@@ -168,20 +172,18 @@ class W3DHub
end end
Async do Async do
internet = Async::HTTP::Internet.instance fetch_game_news(game)
fetch_game_news(internet, game)
populate_game_news(game) populate_game_news(game)
end end
end end
end end
def fetch_game_news(internet, game) def fetch_game_news(game)
news = Api.news(internet, game.id) news = Api.news(game.id)
if news if news
news.items[0..9].each do |item| news.items[0..9].each do |item|
Cache.fetch(internet, item.image) Cache.fetch(item.image)
end end
@game_news[game.id] = news @game_news[game.id] = news

View File

@@ -37,18 +37,20 @@ class W3DHub
# Do network stuff # Do network stuff
Async do Async do
internet = Async::HTTP::Internet.instance account = Api.user_login(@username.value, @password.value)
account = Api.user_login(internet, @username.value, @password.value)
if account if account
Store.account = account Store.account = account
Store.settings[:account][:refresh_token] = account.refresh_token Store.settings[:account][:data] = account
Store.settings.save_settings Store.settings.save_settings
Cache.fetch(internet, account.avatar_uri, true) internet = Async::HTTP::Internet.instance
Cache.fetch(account.avatar_uri, true)
populate_account_info populate_account_info
applications = Api.applications
Store.applications = applications if applications
page(W3DHub::Pages::Games) page(W3DHub::Pages::Games)
else else
# An error occurred, enable account entry # An error occurred, enable account entry
@@ -70,8 +72,7 @@ class W3DHub
if Store.account if Store.account
Async do Async do
internet = Async::HTTP::Internet.instance Cache.fetch(Store.account.avatar_uri)
Cache.fetch(internet, Store.account.avatar_uri)
populate_account_info populate_account_info
page(W3DHub::Pages::Games) page(W3DHub::Pages::Games)
@@ -98,10 +99,13 @@ class W3DHub
end end
def depopulate_account_info def depopulate_account_info
Store.settings[:account][:refresh_token] = nil Store.settings[:account] = {}
Store.settings.save_settings Store.settings.save_settings
Store.account = nil Store.account = nil
applications = Api.applications
Store.applications if applications
@host.instance_variable_get(:"@account_container").clear do @host.instance_variable_get(:"@account_container").clear do
stack(width: 0.7, height: 1.0) do stack(width: 0.7, height: 1.0) do
# background 0xff_222222 # background 0xff_222222

View File

@@ -26,9 +26,11 @@ class W3DHub
@filters.each do |app_id, enabled| @filters.each do |app_id, enabled|
app = Store.applications.games.find { |a| a.id == app_id.to_s } app = Store.applications.games.find { |a| a.id == app_id.to_s }
image "#{GAME_ROOT_PATH}/media/icons/#{app_id}.png", tip: "#{app.name}", height: 1.0, image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/app.png"
image image_path, tip: "#{app.name}", height: 1.0,
border_thickness_bottom: 1, border_color_bottom: 0x00_000000, border_thickness_bottom: 1, border_color_bottom: 0x00_000000,
color: enabled ? 0xff_ffffff : 0xff_444444, hover: { border_color_bottom: 0xff_aaaaaa }, margin_right: 32 do |img| color: enabled ? 0xff_ffffff : 0xff_444444, hover: { border_color_bottom: 0xff_aaaaaa }, margin_right: 16 do |img|
@filters[app_id] = !@filters[app_id] @filters[app_id] = !@filters[app_id]
Store.settings[:server_list_filters] = @filters Store.settings[:server_list_filters] = @filters
Store.settings.save_settings Store.settings.save_settings
@@ -359,9 +361,11 @@ class W3DHub
end end
def game_icon(server) def game_icon(server)
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png" : "#{GAME_ROOT_PATH}/media/icons/app.png"
if server.status.password if server.status.password
@server_locked_icons[server.game] ||= Gosu.render(96, 96) do @server_locked_icons[server.game] ||= Gosu.render(96, 96) do
i = get_image("#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png") i = get_image(image_path)
lock = get_image("#{GAME_ROOT_PATH}/media/ui_icons/locked.png") lock = get_image("#{GAME_ROOT_PATH}/media/ui_icons/locked.png")
scale = [96.0 / i.width, 96.0 / i.height].min scale = [96.0 / i.width, 96.0 / i.height].min
@@ -369,7 +373,7 @@ class W3DHub
lock.draw(96 - lock.width * 0.5, 96 - lock.height * 0.5, 0, 0.5, 0.5, 0xff_ff8800) lock.draw(96 - lock.width * 0.5, 96 - lock.height * 0.5, 0, 0.5, 0.5, 0xff_ff8800)
end end
else else
"#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png" image_path
end end
end end

View File

@@ -32,11 +32,9 @@ class W3DHub
end end
Async do Async do
internet = Async::HTTP::Internet.instance
@tasks.keys.each do |key| @tasks.keys.each do |key|
Sync do Sync do
send(key, internet) send(key)
end end
end end
end end
@@ -56,26 +54,29 @@ class W3DHub
@progressbar.value = @fraction @progressbar.value = @fraction
if @progressbar.value >= 1.0 && @task_index == @tasks.size push_state(States::Interface) if @progressbar.value >= 1.0 && @task_index == @tasks.size
Store.account = @account
Store.service_status = @service_status
Store.applications = @applications
push_state(States::Interface)
end
@task_index += 1 if @tasks.dig(@tasks.keys[@task_index], :complete) @task_index += 1 if @tasks.dig(@tasks.keys[@task_index], :complete)
end end
def refresh_user_token(internet) def refresh_user_token
if Store.settings[:account, :refresh_token] if Store.settings[:account, :data]
@account = Api.refresh_user_login(internet, Store.settings[:account, :refresh_token]) account = Api::Account.new(Store.settings[:account, :data], {})
if (Time.now.to_i - account.access_token_expiry.to_i) >= 60 * 3 # Older than 3 hours then refresh
@account = Api.refresh_user_login(account.refresh_token)
else
@account = account
end
if @account if @account
Store.settings[:account][:refresh_token] = @account.refresh_token Store.account = @account
Cache.fetch(internet, @account.avatar_uri, true)
Store.settings[:account][:data] = @account
Cache.fetch(@account.avatar_uri, true)
else else
Store.settings[:account][:refresh_token] = nil Store.settings[:account] = {}
end end
Store.settings.save_settings Store.settings.save_settings
@@ -86,10 +87,12 @@ class W3DHub
end end
end end
def service_status(internet) def service_status
@service_status = Api.service_status(internet) @service_status = Api.service_status
if @service_status if @service_status
Store.service_status = @service_status
if !@service_status.authentication? || !@service_status.package_download? if !@service_status.authentication? || !@service_status.package_download?
@status_label.value = "Authentication is #{@service_status.authentication? ? 'Okay' : 'Down'}. Package Download is #{@service_status.package_download? ? 'Okay' : 'Down'}." @status_label.value = "Authentication is #{@service_status.authentication? ? 'Okay' : 'Down'}. Package Download is #{@service_status.package_download? ? 'Okay' : 'Down'}."
end end
@@ -100,22 +103,26 @@ class W3DHub
end end
end end
def applications(internet) def applications
@status_label.value = I18n.t(:"boot.checking_for_updates") @status_label.value = I18n.t(:"boot.checking_for_updates")
@applications = Api.applications(internet) @applications = Api.applications
if @applications if @applications
Store.applications = @applications
@tasks[:applications][:complete] = true @tasks[:applications][:complete] = true
else else
# FIXME: Failed to retreive! # FIXME: Failed to retreive!
end end
end end
def server_list(internet) def server_list
@status_label.value = I18n.t(:"server_browser.fetching_server_list") @status_label.value = I18n.t(:"server_browser.fetching_server_list")
begin begin
internet = Async::HTTP::Internet.instance
list = Api.server_list(internet, 2) list = Api.server_list(internet, 2)
if list if list