mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2025-12-16 09:12:35 +00:00
Refactored API to support both backends and to re-enable logging in (on the primary backend)
This commit is contained in:
213
lib/api.rb
213
lib/api.rb
@@ -41,12 +41,26 @@ class W3DHub
|
||||
end
|
||||
|
||||
#! === W3D Hub API === !#
|
||||
W3DHUB_API_ENDPOINT = "https://secure.w3dhub.com".freeze # "https://example.com" # "http://127.0.0.1:9292".freeze #
|
||||
W3DHUB_API_CONNECTION = Excon.new(W3DHUB_API_ENDPOINT, persistent: true)
|
||||
|
||||
ENDPOINT = "https://w3dhub-api.w3d.cyberarm.dev" # "https://secure.w3dhub.com".freeze # "https://example.com" # "http://127.0.0.1:9292".freeze #
|
||||
API_CONNECTION = Excon.new(ENDPOINT, persistent: true)
|
||||
ALT_W3DHUB_API_ENDPOINT = "https://w3dhub-api.w3d.cyberarm.dev".freeze # "https://secure.w3dhub.com".freeze # "https://example.com" # "http://127.0.0.1:9292".freeze #
|
||||
ALT_W3DHUB_API_API_CONNECTION = Excon.new(ALT_W3DHUB_API_ENDPOINT, persistent: true)
|
||||
|
||||
def self.excon(method, url, headers = DEFAULT_HEADERS, body = nil, api = :api)
|
||||
logger.debug(LOG_TAG) { "Fetching #{method.to_s.upcase} \"#{url}\"..." }
|
||||
def self.excon(method, url, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
case backend
|
||||
when :w3dhub
|
||||
connection = W3DHUB_API_CONNECTION
|
||||
endpoint = W3DHUB_API_ENDPOINT
|
||||
when :alt_w3dhub
|
||||
connection = ALT_W3DHUB_API_API_CONNECTION
|
||||
endpoint = ALT_W3DHUB_API_ENDPOINT
|
||||
when :gsh
|
||||
connection = GSH_CONNECTION
|
||||
endpoint = SERVER_LIST_ENDPOINT
|
||||
end
|
||||
|
||||
logger.debug(LOG_TAG) { "Fetching #{method.to_s.upcase} \"#{endpoint}#{url}\"..." }
|
||||
|
||||
# Inject Authorization header if account data is populated
|
||||
if Store.account
|
||||
@@ -55,9 +69,6 @@ class W3DHub
|
||||
headers["Authorization"] = "Bearer #{Store.account.access_token}"
|
||||
end
|
||||
|
||||
connection = api == :api ? API_CONNECTION : GSH_CONNECTION
|
||||
endpoint = api == :api ? ENDPOINT : SERVER_LIST_ENDPOINT
|
||||
|
||||
begin
|
||||
connection.send(
|
||||
method,
|
||||
@@ -86,8 +97,48 @@ class W3DHub
|
||||
end
|
||||
end
|
||||
|
||||
def self.post(url, headers = DEFAULT_HEADERS, body = nil, api = :api)
|
||||
excon(:post, url, headers, body, api)
|
||||
def self.post(url, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
excon(:post, url, headers, body, backend)
|
||||
end
|
||||
|
||||
def self.get(url, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
excon(:get, url, headers, body, backend)
|
||||
end
|
||||
|
||||
# Api.get but handles any URL instead of known hosts
|
||||
def self.fetch(url, headers = DEFAULT_HEADERS, body = nil, backend = nil)
|
||||
uri = URI(url)
|
||||
|
||||
# Use Api.get for `W3DHUB_API_ENDPOINT` URL's to exploit keep alive and connection reuse (faster responses)
|
||||
return excon(:get, url, headers, body, backend) if "#{uri.scheme}://#{uri.host}" == W3DHUB_API_ENDPOINT
|
||||
|
||||
logger.debug(LOG_TAG) { "Fetching GET \"#{url}\"..." }
|
||||
|
||||
begin
|
||||
Excon.get(
|
||||
url,
|
||||
headers: headers,
|
||||
body: body,
|
||||
nonblock: true,
|
||||
tcp_nodelay: true,
|
||||
write_timeout: API_TIMEOUT,
|
||||
read_timeout: API_TIMEOUT,
|
||||
connect_timeout: API_TIMEOUT,
|
||||
idempotent: true,
|
||||
retry_limit: 3,
|
||||
retry_interval: 1,
|
||||
retry_errors: [Excon::Error::Socket, Excon::Error::HTTPStatus] # Don't retry on timeout
|
||||
)
|
||||
rescue Excon::Errors::Timeout => e
|
||||
logger.error(LOG_TAG) { "Connection to \"#{url}\" timed out after: #{API_TIMEOUT} seconds" }
|
||||
|
||||
DummyResponse.new(e)
|
||||
rescue Excon::Error => e
|
||||
logger.error(LOG_TAG) { "Connection to \"#{url}\" errored:" }
|
||||
logger.error(LOG_TAG) { e }
|
||||
|
||||
DummyResponse.new(e)
|
||||
end
|
||||
end
|
||||
|
||||
# Method: POST
|
||||
@@ -105,24 +156,16 @@ class W3DHub
|
||||
#
|
||||
# On a failed login the service responds with:
|
||||
# {"error":"login-failed"}
|
||||
def self.refresh_user_login(refresh_token)
|
||||
def self.refresh_user_login(refresh_token, backend = :w3dhub)
|
||||
body = "data=#{JSON.dump({refreshToken: refresh_token})}"
|
||||
response = post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body)
|
||||
response = post("/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body, backend)
|
||||
|
||||
if response.status == 200
|
||||
user_data = JSON.parse(response.body, symbolize_names: true)
|
||||
|
||||
return false if user_data[:error]
|
||||
|
||||
body = "data=#{JSON.dump({ id: user_data[:userid] })}"
|
||||
user_details = post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body)
|
||||
|
||||
if user_details.status == 200
|
||||
user_details_data = JSON.parse(user_details.body, symbolize_names: true)
|
||||
else
|
||||
logger.error(LOG_TAG) { "Failed to fetch refresh user details:" }
|
||||
logger.error(LOG_TAG) { user_details }
|
||||
end
|
||||
user_details_data = user_details(user_data[:userid]) || {}
|
||||
|
||||
Account.new(user_data, user_details_data)
|
||||
else
|
||||
@@ -133,24 +176,16 @@ class W3DHub
|
||||
end
|
||||
|
||||
# See #user_refresh_token
|
||||
def self.user_login(username, password)
|
||||
def self.user_login(username, password, backend = :w3dhub)
|
||||
body = "data=#{JSON.dump({username: username, password: password})}"
|
||||
response = post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body)
|
||||
response = post("/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body, backend)
|
||||
|
||||
if response.status == 200
|
||||
user_data = JSON.parse(response.body, symbolize_names: true)
|
||||
|
||||
return false if user_data[:error]
|
||||
|
||||
body = "data=#{JSON.dump({ id: user_data[:userid] })}"
|
||||
user_details = post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body)
|
||||
|
||||
if user_details.status == 200
|
||||
user_details_data = JSON.parse(user_details.body, symbolize_names: true)
|
||||
else
|
||||
logger.error(LOG_TAG) { "Failed to fetch user details:" }
|
||||
logger.error(LOG_TAG) { user_details }
|
||||
end
|
||||
user_details_data = user_details(user_data[:userid]) || {}
|
||||
|
||||
Account.new(user_data, user_details_data)
|
||||
else
|
||||
@@ -160,18 +195,27 @@ class W3DHub
|
||||
end
|
||||
end
|
||||
|
||||
# /apis/launcher/1/user-login
|
||||
# Client sends an Authorization header bearer token which is received from logging in (Required?)
|
||||
# /apis/w3dhub/1/get-user-details
|
||||
#
|
||||
# Response: avatar-uri (Image download uri), id, username
|
||||
def self.user_details(id)
|
||||
def self.user_details(id, backend = :w3dhub)
|
||||
body = "data=#{JSON.dump({ id: id })}"
|
||||
user_details = post("/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body, backend)
|
||||
|
||||
if user_details.status == 200
|
||||
JSON.parse(user_details.body, symbolize_names: true)
|
||||
else
|
||||
logger.error(LOG_TAG) { "Failed to fetch user details:" }
|
||||
logger.error(LOG_TAG) { user_details }
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# /apis/w3dhub/1/get-service-status
|
||||
# Service response:
|
||||
# {"services":{"authentication":true,"packageDownload":true}}
|
||||
def self.service_status
|
||||
response = post("#{ENDPOINT}/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS)
|
||||
def self.service_status(backend = :w3dhub)
|
||||
response = post("/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS, nil, backend)
|
||||
|
||||
if response.status == 200
|
||||
ServiceStatus.new(response.body)
|
||||
@@ -186,8 +230,8 @@ class W3DHub
|
||||
# Client sends an Authorization header bearer token which is received from logging in (Optional)
|
||||
# Launcher sends an empty data request: data={}
|
||||
# Response is a list of applications/games
|
||||
def self.applications
|
||||
response = post("#{ENDPOINT}/apis/launcher/1/get-applications")
|
||||
def self.applications(backend = :w3dhub)
|
||||
response = post("/apis/launcher/1/get-applications", DEFAULT_HEADERS, nil, backend)
|
||||
|
||||
if response.status == 200
|
||||
Applications.new(response.body)
|
||||
@@ -198,13 +242,82 @@ class W3DHub
|
||||
end
|
||||
end
|
||||
|
||||
# Populate applications list from primary and alternate backends
|
||||
# (alternate only has latest public builds of _most_ games)
|
||||
def self._applications
|
||||
applications_primary = Store.account ? Api.applications(:w3dhub) : false
|
||||
applications_alternate = Api.applications(:alt_w3dhub)
|
||||
|
||||
# Fail if we fail to fetch applications list from either backend
|
||||
return false unless applications_primary || applications_alternate
|
||||
|
||||
return applications_alternate unless applications_primary
|
||||
|
||||
# Merge the two app lists together
|
||||
apps = applications_alternate
|
||||
if applications_primary
|
||||
applications_primary.games.each do |game|
|
||||
# Check if game exists in alternate list
|
||||
_game = apps.games.find { |g| g.id == game.id }
|
||||
unless _game
|
||||
apps.games << game
|
||||
|
||||
# App didn't exist in alternates list
|
||||
# comparing channels isn't useful
|
||||
next
|
||||
end
|
||||
|
||||
# If it does, check that all of its channels also exist in alternate list
|
||||
# and that the primary versions are the same as the alternates list
|
||||
game.channels.each do |channel|
|
||||
_channel = _game.channels.find { |c| c.id == channel.id }
|
||||
|
||||
unless _channel
|
||||
_game.channels << channel
|
||||
|
||||
# App didn't have channel in alternates list
|
||||
# comparing channel isn't useful
|
||||
next
|
||||
end
|
||||
|
||||
# If channel versions and access levels match then all's well
|
||||
if channel.current_version == _channel.current_version &&
|
||||
channel.user_level == _channel.user_level
|
||||
|
||||
# All's Well!
|
||||
next
|
||||
end
|
||||
|
||||
# If the access levels doen't match then overwrite alternate's channel with primary's channel
|
||||
if channel.user_level != _channel.user_level
|
||||
# Replace alternate's channel with primary's channel
|
||||
_game.channels[_game.channels.index(_channel)] = channel
|
||||
|
||||
# Replaced, continue.
|
||||
next
|
||||
end
|
||||
|
||||
# If versions doen't match then pick whichever one is higher
|
||||
if Gem::Version.new(channel.current_version) > Gem::Version.new(_channel.current_version)
|
||||
# Replace alternate's channel with primary's channel
|
||||
_game.channels[_game.channels.index(_channel)] = channel
|
||||
else
|
||||
# Do nothing, alternate backend version is greater.
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
apps
|
||||
end
|
||||
|
||||
# /apis/w3dhub/1/get-news
|
||||
# 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)
|
||||
# Response is a JSON hash with a "highlighted" and "news" keys; the "news" one seems to be the desired one
|
||||
def self.news(category)
|
||||
def self.news(category, backend = :w3dhub)
|
||||
body = "data=#{JSON.dump({category: category})}"
|
||||
response = post("#{ENDPOINT}/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body)
|
||||
response = post("/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body, backend)
|
||||
|
||||
if response.status == 200
|
||||
News.new(response.body)
|
||||
@@ -220,9 +333,9 @@ class W3DHub
|
||||
|
||||
# /apis/launcher/1/get-package-details
|
||||
# client requests package details: data={"packages":[{"category":"games","name":"apb.ico","subcategory":"apb","version":""}]}
|
||||
def self.package_details(packages)
|
||||
def self.package_details(packages, backend = :w3dhub)
|
||||
body = URI.encode_www_form("data": JSON.dump({ packages: packages }))
|
||||
response = post("#{ENDPOINT}/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body)
|
||||
response = post("/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body, backend)
|
||||
|
||||
if response.status == 200
|
||||
hash = JSON.parse(response.body, symbolize_names: true)
|
||||
@@ -247,9 +360,9 @@ class W3DHub
|
||||
# /apis/w3dhub/1/get-events
|
||||
#
|
||||
# clients requests events: data={"serverPath":"apb"}
|
||||
def self.events(app_id)
|
||||
def self.events(app_id, backend = :w3dhub)
|
||||
body = URI.encode_www_form("data": JSON.dump({ serverPath: app_id }))
|
||||
response = post("#{ENDPOINT}/apis/w3dhub/1/get-server-events", FORM_ENCODED_HEADERS, body)
|
||||
response = post("/apis/w3dhub/1/get-server-events", FORM_ENCODED_HEADERS, body, backend)
|
||||
|
||||
if response.status == 200
|
||||
array = JSON.parse(response.body, symbolize_names: true)
|
||||
@@ -266,10 +379,6 @@ class W3DHub
|
||||
# SERVER_LIST_ENDPOINT = "http://127.0.0.1:9292".freeze
|
||||
GSH_CONNECTION = Excon.new(SERVER_LIST_ENDPOINT, persistent: true)
|
||||
|
||||
def self.get(url, headers = DEFAULT_HEADERS, body = nil, api = :api)
|
||||
excon(:get, url, headers, body, api)
|
||||
end
|
||||
|
||||
# Method: GET
|
||||
# FORMAT: JSON
|
||||
|
||||
@@ -287,8 +396,8 @@ class W3DHub
|
||||
# id, name, score, kills, deaths
|
||||
# ...players[]:
|
||||
# nick, team (index of teams array), score, kills, deaths
|
||||
def self.server_list(level = 1)
|
||||
response = get("#{SERVER_LIST_ENDPOINT}/listings/getAll/v2?statusLevel=#{level}", DEFAULT_HEADERS, nil, :gsh)
|
||||
def self.server_list(level = 1, backend = :gsh)
|
||||
response = get("/listings/getAll/v2?statusLevel=#{level}", DEFAULT_HEADERS, nil, backend)
|
||||
|
||||
if response.status == 200
|
||||
data = JSON.parse(response.body, symbolize_names: true)
|
||||
@@ -309,10 +418,10 @@ class W3DHub
|
||||
# id, name, score, kills, deaths
|
||||
# ...players[]:
|
||||
# nick, team (index of teams array), score, kills, deaths
|
||||
def self.server_details(id, level)
|
||||
def self.server_details(id, level, backend = :gsh)
|
||||
return false unless id && level
|
||||
|
||||
response = get("#{SERVER_LIST_ENDPOINT}/listings/getStatus/v2/#{id}?statusLevel=#{level}", DEFAULT_HEADERS, nil, :gsh)
|
||||
response = get("/listings/getStatus/v2/#{id}?statusLevel=#{level}", DEFAULT_HEADERS, nil, backend)
|
||||
|
||||
if response.status == 200
|
||||
hash = JSON.parse(response.body, symbolize_names: true)
|
||||
|
||||
Reference in New Issue
Block a user