mirror of
https://github.com/rfvgyhn/min-ed-launcher.git
synced 2026-02-04 16:15:45 +00:00
provide better output when frontier unable to verify ownership of game
This commit is contained in:
@ -4,6 +4,9 @@
|
||||
|
||||
### New Features
|
||||
- Add ability to download products if they aren't yet installed
|
||||
- Show a more helpful message instead of generic JSON error when Frontier API couldn't verify game ownership.
|
||||
|
||||
This error happens intermittently with Steam licenses.
|
||||
|
||||
## [0.9.0] - 2023-09-12
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
module MinEdLauncher.Api
|
||||
|
||||
open System
|
||||
open System.IO
|
||||
open System.IO.Compression
|
||||
open System.Net
|
||||
open System.Net.Http
|
||||
@ -32,6 +33,7 @@ type AuthResult =
|
||||
| LinkAvailable of Uri
|
||||
| Denied of string
|
||||
| Failed of string
|
||||
| CouldntConfirmOwnership
|
||||
|
||||
let private buildUri (host: Uri) (path: string) queryParams =
|
||||
let builder = UriBuilder(host)
|
||||
@ -169,6 +171,13 @@ let authenticate (runningTime: unit -> double) (token: AuthToken) platform machi
|
||||
match info with
|
||||
| Error m -> return Failed m
|
||||
| Ok (path, query, parseMachineToken) ->
|
||||
let isHtml (stream: Stream) =
|
||||
let htmlHeader = "<!DOCTYPE html";
|
||||
let buffer = Array.zeroCreate(htmlHeader.Length);
|
||||
stream.Read(buffer, 0, buffer.Length) |> ignore
|
||||
stream.Seek(0, SeekOrigin.Begin) |> ignore
|
||||
Encoding.UTF8.GetString(buffer) = htmlHeader
|
||||
|
||||
use request = new HttpRequestMessage()
|
||||
request.RequestUri <- buildUri httpClient.BaseAddress path query
|
||||
match platform with
|
||||
@ -178,54 +187,60 @@ let authenticate (runningTime: unit -> double) (token: AuthToken) platform machi
|
||||
|
||||
use! response = httpClient.SendAsync(request)
|
||||
use! content = response.Content.ReadAsStreamAsync()
|
||||
let content = content |> Json.parseStream >>= Json.rootElement
|
||||
let mapResult f = function
|
||||
| Ok value -> f value
|
||||
| Error msg -> Failed msg
|
||||
let parseError content =
|
||||
let errorValue = content >>= Json.parseEitherProp "error_enum" "errorCode" >>= Json.toString |> Result.defaultValue "Unknown"
|
||||
let errorMessage = content >>= Json.parseProp "message" >>= Json.toString |> Result.defaultValue ""
|
||||
errorValue, errorMessage
|
||||
|
||||
return
|
||||
match response.StatusCode with
|
||||
| code when int code < 300 ->
|
||||
let fdevAuthToken = content >>= Json.parseProp "authToken" >>= Json.toString
|
||||
let machineToken = parseMachineToken content
|
||||
let registeredName = content >>= Json.parseProp "registeredName"
|
||||
>>= Json.toString
|
||||
|> Result.defaultValue $"%s{platform.Name} User"
|
||||
let errorValue = content >>= Json.parseEitherProp "error_enum" "errorCode" >>= Json.toString
|
||||
let errorMessage = content >>= Json.parseProp "message" >>= Json.toString
|
||||
|
||||
match fdevAuthToken, machineToken, errorValue, errorMessage with
|
||||
| Error _, Error _, Ok value, Ok msg -> Failed $"%s{value} - %s{msg}"
|
||||
| Ok fdevToken, Ok machineToken, _, _ ->
|
||||
let session = { Token = fdevToken; PlatformToken = token; Name = registeredName; MachineToken = machineToken }
|
||||
Authorized <| new Connection(httpClient, session, runningTime)
|
||||
| Error msg, _, _, _
|
||||
| _, Error msg, _, _ -> Failed msg
|
||||
| HttpStatusCode.Found ->
|
||||
content >>= Json.parseProp "Location"
|
||||
>>= Json.asUri
|
||||
|> mapResult RegistrationRequired
|
||||
| HttpStatusCode.TemporaryRedirect ->
|
||||
content >>= Json.parseProp "Location"
|
||||
>>= Json.asUri
|
||||
|> mapResult LinkAvailable
|
||||
| HttpStatusCode.BadRequest ->
|
||||
let errValue, errMessage = content |> parseError
|
||||
$"Bad Request: %s{errValue} - %s{errMessage}" |> Denied
|
||||
| HttpStatusCode.Forbidden ->
|
||||
let errValue, errMessage = content |> parseError
|
||||
$"Forbidden: %s{errValue} - %s{errMessage}" |> Denied
|
||||
| HttpStatusCode.Unauthorized ->
|
||||
let errValue, errMessage = content |> parseError
|
||||
$"Unauthorized: %s{errValue} - %s{errMessage}" |> Denied
|
||||
| HttpStatusCode.ServiceUnavailable ->
|
||||
Failed "Service unavailable"
|
||||
| code ->
|
||||
Failed $"%i{int code}: %s{response.ReasonPhrase}"
|
||||
// API returns an HTML login page when it can't verify game ownership. Sometimes happens with Steam accounts
|
||||
// API doesn't respond with Content-Type header so check the first few bytes
|
||||
if isHtml content then
|
||||
return CouldntConfirmOwnership
|
||||
else
|
||||
let content = content |> Json.parseStream >>= Json.rootElement
|
||||
let mapResult f = function
|
||||
| Ok value -> f value
|
||||
| Error msg -> Failed msg
|
||||
let parseError content =
|
||||
let errorValue = content >>= Json.parseEitherProp "error_enum" "errorCode" >>= Json.toString |> Result.defaultValue "Unknown"
|
||||
let errorMessage = content >>= Json.parseProp "message" >>= Json.toString |> Result.defaultValue ""
|
||||
errorValue, errorMessage
|
||||
|
||||
return
|
||||
match response.StatusCode with
|
||||
| code when int code < 300 ->
|
||||
let fdevAuthToken = content >>= Json.parseProp "authToken" >>= Json.toString
|
||||
let machineToken = parseMachineToken content
|
||||
let registeredName = content >>= Json.parseProp "registeredName"
|
||||
>>= Json.toString
|
||||
|> Result.defaultValue $"%s{platform.Name} User"
|
||||
let errorValue = content >>= Json.parseEitherProp "error_enum" "errorCode" >>= Json.toString
|
||||
let errorMessage = content >>= Json.parseProp "message" >>= Json.toString
|
||||
|
||||
match fdevAuthToken, machineToken, errorValue, errorMessage with
|
||||
| Error _, Error _, Ok value, Ok msg -> Failed $"%s{value} - %s{msg}"
|
||||
| Ok fdevToken, Ok machineToken, _, _ ->
|
||||
let session = { Token = fdevToken; PlatformToken = token; Name = registeredName; MachineToken = machineToken }
|
||||
Authorized <| new Connection(httpClient, session, runningTime)
|
||||
| Error msg, _, _, _
|
||||
| _, Error msg, _, _ -> Failed msg
|
||||
| HttpStatusCode.Found ->
|
||||
content >>= Json.parseProp "Location"
|
||||
>>= Json.asUri
|
||||
|> mapResult RegistrationRequired
|
||||
| HttpStatusCode.TemporaryRedirect ->
|
||||
content >>= Json.parseProp "Location"
|
||||
>>= Json.asUri
|
||||
|> mapResult LinkAvailable
|
||||
| HttpStatusCode.BadRequest ->
|
||||
let errValue, errMessage = content |> parseError
|
||||
$"Bad Request: %s{errValue} - %s{errMessage}" |> Denied
|
||||
| HttpStatusCode.Forbidden ->
|
||||
let errValue, errMessage = content |> parseError
|
||||
$"Forbidden: %s{errValue} - %s{errMessage}" |> Denied
|
||||
| HttpStatusCode.Unauthorized ->
|
||||
let errValue, errMessage = content |> parseError
|
||||
$"Unauthorized: %s{errValue} - %s{errMessage}" |> Denied
|
||||
| HttpStatusCode.ServiceUnavailable ->
|
||||
Failed "Service unavailable"
|
||||
| code ->
|
||||
Failed $"%i{int code}: %s{response.ReasonPhrase}"
|
||||
}
|
||||
|
||||
let getAuthorizedProducts platform lang (connection: Connection) = task {
|
||||
|
||||
@ -14,6 +14,7 @@ open FsToolkit.ErrorHandling
|
||||
|
||||
type LoginError =
|
||||
| ActionRequired of string
|
||||
| CouldntConfirmOwnership of Platform
|
||||
| Failure of string
|
||||
let login launcherVersion runningTime httpClient machineId (platform: Platform) lang =
|
||||
let authenticate disposable = function
|
||||
@ -27,7 +28,9 @@ let login launcherVersion runningTime httpClient machineId (platform: Platform)
|
||||
| Api.RegistrationRequired uri -> return ActionRequired <| $"Registration is required at %A{uri}" |> Error
|
||||
| Api.LinkAvailable uri -> return ActionRequired <| $"Link available at %A{uri}" |> Error
|
||||
| Api.Denied msg -> return Failure msg |> Error
|
||||
| Api.Failed msg -> return Failure msg |> Error }
|
||||
| Api.Failed msg -> return Failure msg |> Error
|
||||
| Api.CouldntConfirmOwnership -> return CouldntConfirmOwnership platform |> Error
|
||||
}
|
||||
| Error msg -> Failure msg |> Error |> Task.fromResult
|
||||
|
||||
match platform with
|
||||
@ -247,6 +250,20 @@ module AppError =
|
||||
| AuthorizedProducts m -> $"Couldn't get available products: %s{m}"
|
||||
| Login (ActionRequired m) -> $"Unsupported login action required: %s{m}"
|
||||
| Login (Failure m) -> $"Couldn't login: %s{m}"
|
||||
| Login (CouldntConfirmOwnership platform) ->
|
||||
let possibleFixes =
|
||||
[
|
||||
$"Ensure you've linked your {platform.Name} account to your Frontier account. https://user.frontierstore.net/user/info"
|
||||
if platform = Steam then
|
||||
"Restart Steam"
|
||||
"Log out and log back in to Steam"
|
||||
"Restart your computer"
|
||||
"Wait a minute or two and retry"
|
||||
"Wait longer"
|
||||
]
|
||||
|> List.map (fun s -> " " + s)
|
||||
|> String.join Environment.NewLine
|
||||
$"Frontier was unable to verify that you own the game. This happens intermittently. Possible fixes include:{Environment.NewLine}{possibleFixes}"
|
||||
| NoSelectedProduct -> "No selected project"
|
||||
| InvalidProductState m -> $"Couldn't start selected product: %s{m}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user