Complete resilient Steam login flow
This commit is contained in:
+52
-15
@@ -42,7 +42,9 @@ var poll_timer: Timer
|
|||||||
var mode := "login"
|
var mode := "login"
|
||||||
var reset_requested := false
|
var reset_requested := false
|
||||||
var steam_transaction_id := ""
|
var steam_transaction_id := ""
|
||||||
var steam_poll_attempts := 0
|
var steam_poll_deadline_msec := 0
|
||||||
|
var steam_poll_failures := 0
|
||||||
|
var steam_popup_opened := false
|
||||||
var automatic_login_pending := false
|
var automatic_login_pending := false
|
||||||
var login_attempt_username := ""
|
var login_attempt_username := ""
|
||||||
var compact_layout := false
|
var compact_layout := false
|
||||||
@@ -73,6 +75,7 @@ func _ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func on_show(payload: Variant = null) -> void:
|
func on_show(payload: Variant = null) -> void:
|
||||||
|
_reset_steam_flow()
|
||||||
_set_busy(false)
|
_set_busy(false)
|
||||||
_set_mode("login")
|
_set_mode("login")
|
||||||
_clear_message()
|
_clear_message()
|
||||||
@@ -84,7 +87,7 @@ func on_show(payload: Variant = null) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func on_hide() -> void:
|
func on_hide() -> void:
|
||||||
poll_timer.stop()
|
_reset_steam_flow()
|
||||||
|
|
||||||
|
|
||||||
func _build_ui() -> void:
|
func _build_ui() -> void:
|
||||||
@@ -407,6 +410,8 @@ func _add_dot_grid(origin: Vector2) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _set_mode(next_mode: String) -> void:
|
func _set_mode(next_mode: String) -> void:
|
||||||
|
if mode == "steam" and next_mode != "steam":
|
||||||
|
_reset_steam_flow()
|
||||||
mode = next_mode
|
mode = next_mode
|
||||||
if login_view == null: return
|
if login_view == null: return
|
||||||
login_view.visible = mode == "login"
|
login_view.visible = mode == "login"
|
||||||
@@ -601,23 +606,30 @@ func _on_reset_confirmed(result: int, code: int, _headers: PackedStringArray, bo
|
|||||||
|
|
||||||
func _start_steam() -> void:
|
func _start_steam() -> void:
|
||||||
if OS.has_feature("web"):
|
if OS.has_feature("web"):
|
||||||
JavaScriptBridge.eval("window.medmindSteamWindow = window.open('about:blank', 'medmind_steam');", true)
|
var popup_result: Variant = JavaScriptBridge.eval("window.ranazeSteamWindow = window.open('about:blank', 'ranaze_steam'); window.ranazeSteamWindow !== null;", true)
|
||||||
|
steam_popup_opened = bool(popup_result)
|
||||||
|
if not steam_popup_opened:
|
||||||
|
return _show_error("Steam sign in needs a popup. Allow popups for this site, then try again.")
|
||||||
_send_json("steam_start", "/steam/start", {}, "Opening Steam...")
|
_send_json("steam_start", "/steam/start", {}, "Opening Steam...")
|
||||||
|
|
||||||
|
|
||||||
func _on_steam_started(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
func _on_steam_started(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
_set_busy(false)
|
_set_busy(false)
|
||||||
if result != HTTPRequest.RESULT_SUCCESS or code < 200 or code >= 300:
|
if result != HTTPRequest.RESULT_SUCCESS or code < 200 or code >= 300:
|
||||||
|
_close_steam_popup()
|
||||||
return _show_error("Unable to start Steam sign in.")
|
return _show_error("Unable to start Steam sign in.")
|
||||||
var data := _parse_dictionary(body)
|
var data := _parse_dictionary(body)
|
||||||
steam_transaction_id = str(data.get("transactionId", ""))
|
steam_transaction_id = str(data.get("transactionId", ""))
|
||||||
var auth_url := str(data.get("authUrl", ""))
|
var auth_url := str(data.get("authUrl", ""))
|
||||||
if steam_transaction_id.is_empty() or auth_url.is_empty(): return _show_error("Steam returned an invalid sign-in response.")
|
if steam_transaction_id.is_empty() or auth_url.is_empty():
|
||||||
|
_close_steam_popup()
|
||||||
|
return _show_error("Steam returned an invalid sign-in response.")
|
||||||
if OS.has_feature("web"):
|
if OS.has_feature("web"):
|
||||||
JavaScriptBridge.eval("if (window.medmindSteamWindow) { window.medmindSteamWindow.location.href = %s; } else { window.location.href = %s; }" % [JSON.stringify(auth_url), JSON.stringify(auth_url)], true)
|
JavaScriptBridge.eval("if (window.ranazeSteamWindow && !window.ranazeSteamWindow.closed) { window.ranazeSteamWindow.location.href = %s; }" % JSON.stringify(auth_url), true)
|
||||||
else:
|
else:
|
||||||
OS.shell_open(auth_url)
|
OS.shell_open(auth_url)
|
||||||
steam_poll_attempts = 0
|
steam_poll_deadline_msec = Time.get_ticks_msec() + 300000
|
||||||
|
steam_poll_failures = 0
|
||||||
_show_success("Complete sign in in the Steam window. Waiting for verification...")
|
_show_success("Complete sign in in the Steam window. Waiting for verification...")
|
||||||
poll_timer.start()
|
poll_timer.start()
|
||||||
|
|
||||||
@@ -625,21 +637,32 @@ func _on_steam_started(result: int, code: int, _headers: PackedStringArray, body
|
|||||||
func _poll_steam() -> void:
|
func _poll_steam() -> void:
|
||||||
var status_request: HTTPRequest = requests["steam_status"]
|
var status_request: HTTPRequest = requests["steam_status"]
|
||||||
if steam_transaction_id.is_empty() or status_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED: return
|
if steam_transaction_id.is_empty() or status_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED: return
|
||||||
steam_poll_attempts += 1
|
if steam_poll_deadline_msec > 0 and Time.get_ticks_msec() >= steam_poll_deadline_msec:
|
||||||
if steam_poll_attempts > 150:
|
_reset_steam_flow()
|
||||||
poll_timer.stop(); return _show_error("Steam sign in timed out. Try again when ready.")
|
return _show_error("Steam sign in timed out. Try again when ready.")
|
||||||
var error: Error = status_request.request("%s/steam/status/%s" % [auth_base_url, steam_transaction_id])
|
var error: Error = status_request.request("%s/steam/status/%s" % [auth_base_url, steam_transaction_id])
|
||||||
if error != OK: _show_error("Waiting for a stable connection to Steam...")
|
if error != OK:
|
||||||
|
steam_poll_failures += 1
|
||||||
|
if steam_poll_failures >= 3: _show_error("Connection interrupted. Still waiting for Steam...")
|
||||||
|
|
||||||
|
|
||||||
func _on_steam_status(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
func _on_steam_status(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
if result != HTTPRequest.RESULT_SUCCESS or code >= 500: return
|
if result != HTTPRequest.RESULT_SUCCESS or code >= 500 or code == 429:
|
||||||
|
steam_poll_failures += 1
|
||||||
|
if steam_poll_failures >= 3: _show_error("Connection interrupted. Still waiting for Steam...")
|
||||||
|
return
|
||||||
if code == 404:
|
if code == 404:
|
||||||
poll_timer.stop(); return _show_error("Steam sign in expired. Please try again.")
|
_reset_steam_flow()
|
||||||
|
return _show_error("Steam sign in expired. Please try again.")
|
||||||
|
if code < 200 or code >= 300:
|
||||||
|
_reset_steam_flow()
|
||||||
|
return _show_error(_response_message(body, "Steam sign in could not be completed."))
|
||||||
|
steam_poll_failures = 0
|
||||||
var data := _parse_dictionary(body)
|
var data := _parse_dictionary(body)
|
||||||
match str(data.get("status", "pending")):
|
match str(data.get("status", "pending")):
|
||||||
"requires_account": poll_timer.stop(); _set_mode("steam")
|
"pending": _show_success("Complete sign in in the Steam window. Waiting for verification...")
|
||||||
"complete": poll_timer.stop(); _accept_session(body, str(data.get("username", "")))
|
"requires_account": poll_timer.stop(); _close_steam_popup(); _set_mode("steam")
|
||||||
|
"complete": poll_timer.stop(); _close_steam_popup(); _accept_session(body, str(data.get("username", "")))
|
||||||
|
|
||||||
|
|
||||||
func _on_steam_action_changed(index: int) -> void:
|
func _on_steam_action_changed(index: int) -> void:
|
||||||
@@ -665,11 +688,25 @@ func _on_steam_completed(result: int, code: int, _headers: PackedStringArray, bo
|
|||||||
_set_busy(false)
|
_set_busy(false)
|
||||||
if result != HTTPRequest.RESULT_SUCCESS or code < 200 or code >= 300:
|
if result != HTTPRequest.RESULT_SUCCESS or code < 200 or code >= 300:
|
||||||
return _show_error(_response_message(body, "Unable to connect this Steam account."))
|
return _show_error(_response_message(body, "Unable to connect this Steam account."))
|
||||||
steam_poll_attempts = 0
|
steam_poll_failures = 0
|
||||||
poll_timer.start()
|
poll_timer.start()
|
||||||
_poll_steam()
|
_poll_steam()
|
||||||
|
|
||||||
|
|
||||||
|
func _reset_steam_flow() -> void:
|
||||||
|
if poll_timer != null: poll_timer.stop()
|
||||||
|
steam_transaction_id = ""
|
||||||
|
steam_poll_deadline_msec = 0
|
||||||
|
steam_poll_failures = 0
|
||||||
|
_close_steam_popup()
|
||||||
|
|
||||||
|
|
||||||
|
func _close_steam_popup() -> void:
|
||||||
|
if OS.has_feature("web") and steam_popup_opened:
|
||||||
|
JavaScriptBridge.eval("if (window.ranazeSteamWindow && !window.ranazeSteamWindow.closed) { window.ranazeSteamWindow.close(); } window.ranazeSteamWindow = null;", true)
|
||||||
|
steam_popup_opened = false
|
||||||
|
|
||||||
|
|
||||||
func _accept_session(body: PackedByteArray, fallback_username: String) -> void:
|
func _accept_session(body: PackedByteArray, fallback_username: String) -> void:
|
||||||
var response := _parse_dictionary(body)
|
var response := _parse_dictionary(body)
|
||||||
var token := str(response.get("accessToken", ""))
|
var token := str(response.get("accessToken", ""))
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
var failures := 0
|
||||||
|
var login: Control
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
login = load("res://scripts/ui/login_screen.gd").new()
|
||||||
|
root.add_child(login)
|
||||||
|
await process_frame
|
||||||
|
_test_elapsed_timeout_clears_flow()
|
||||||
|
_test_transient_failure_keeps_transaction()
|
||||||
|
_test_leaving_completion_cancels_flow()
|
||||||
|
login.queue_free()
|
||||||
|
await process_frame
|
||||||
|
quit(1 if failures > 0 else 0)
|
||||||
|
|
||||||
|
|
||||||
|
func _test_elapsed_timeout_clears_flow() -> void:
|
||||||
|
login.steam_transaction_id = "expired-transaction"
|
||||||
|
login.steam_poll_deadline_msec = 1
|
||||||
|
login.poll_timer.start()
|
||||||
|
login._poll_steam()
|
||||||
|
_assert(login.steam_transaction_id.is_empty(), "elapsed Steam login deadline should clear the transaction")
|
||||||
|
_assert(login.poll_timer.is_stopped(), "elapsed Steam login deadline should stop polling")
|
||||||
|
|
||||||
|
|
||||||
|
func _test_transient_failure_keeps_transaction() -> void:
|
||||||
|
login.steam_transaction_id = "retry-transaction"
|
||||||
|
login.steam_poll_deadline_msec = Time.get_ticks_msec() + 300000
|
||||||
|
login.steam_poll_failures = 2
|
||||||
|
login._on_steam_status(HTTPRequest.RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray())
|
||||||
|
_assert(login.steam_transaction_id == "retry-transaction", "temporary connection failures should preserve the Steam transaction")
|
||||||
|
_assert(login.steam_poll_failures == 3, "temporary connection failures should be tracked without consuming the deadline")
|
||||||
|
|
||||||
|
|
||||||
|
func _test_leaving_completion_cancels_flow() -> void:
|
||||||
|
login.mode = "steam"
|
||||||
|
login.steam_transaction_id = "cancel-transaction"
|
||||||
|
login.poll_timer.start()
|
||||||
|
login._set_mode("login")
|
||||||
|
_assert(login.steam_transaction_id.is_empty(), "leaving Steam account completion should clear its transaction")
|
||||||
|
_assert(login.poll_timer.is_stopped(), "leaving Steam account completion should stop polling")
|
||||||
|
|
||||||
|
|
||||||
|
func _assert(condition: bool, message: String) -> void:
|
||||||
|
if condition:
|
||||||
|
return
|
||||||
|
failures += 1
|
||||||
|
push_error(message)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cnqc7wymmiwdd
|
||||||
Reference in New Issue
Block a user