Complete resilient Steam login flow

This commit is contained in:
hz
2026-07-20 00:30:06 -05:00
parent d176640cff
commit f93b7ac380
3 changed files with 107 additions and 15 deletions
+52 -15
View File
@@ -42,7 +42,9 @@ var poll_timer: Timer
var mode := "login"
var reset_requested := false
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 login_attempt_username := ""
var compact_layout := false
@@ -73,6 +75,7 @@ func _ready() -> void:
func on_show(payload: Variant = null) -> void:
_reset_steam_flow()
_set_busy(false)
_set_mode("login")
_clear_message()
@@ -84,7 +87,7 @@ func on_show(payload: Variant = null) -> void:
func on_hide() -> void:
poll_timer.stop()
_reset_steam_flow()
func _build_ui() -> void:
@@ -407,6 +410,8 @@ func _add_dot_grid(origin: Vector2) -> void:
func _set_mode(next_mode: String) -> void:
if mode == "steam" and next_mode != "steam":
_reset_steam_flow()
mode = next_mode
if login_view == null: return
login_view.visible = mode == "login"
@@ -601,23 +606,30 @@ func _on_reset_confirmed(result: int, code: int, _headers: PackedStringArray, bo
func _start_steam() -> void:
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...")
func _on_steam_started(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
_set_busy(false)
if result != HTTPRequest.RESULT_SUCCESS or code < 200 or code >= 300:
_close_steam_popup()
return _show_error("Unable to start Steam sign in.")
var data := _parse_dictionary(body)
steam_transaction_id = str(data.get("transactionId", ""))
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"):
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:
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...")
poll_timer.start()
@@ -625,21 +637,32 @@ func _on_steam_started(result: int, code: int, _headers: PackedStringArray, body
func _poll_steam() -> void:
var status_request: HTTPRequest = requests["steam_status"]
if steam_transaction_id.is_empty() or status_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED: return
steam_poll_attempts += 1
if steam_poll_attempts > 150:
poll_timer.stop(); return _show_error("Steam sign in timed out. Try again when ready.")
if steam_poll_deadline_msec > 0 and Time.get_ticks_msec() >= steam_poll_deadline_msec:
_reset_steam_flow()
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])
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:
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:
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)
match str(data.get("status", "pending")):
"requires_account": poll_timer.stop(); _set_mode("steam")
"complete": poll_timer.stop(); _accept_session(body, str(data.get("username", "")))
"pending": _show_success("Complete sign in in the Steam window. Waiting for verification...")
"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:
@@ -665,11 +688,25 @@ func _on_steam_completed(result: int, code: int, _headers: PackedStringArray, bo
_set_busy(false)
if result != HTTPRequest.RESULT_SUCCESS or code < 200 or code >= 300:
return _show_error(_response_message(body, "Unable to connect this Steam account."))
steam_poll_attempts = 0
steam_poll_failures = 0
poll_timer.start()
_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:
var response := _parse_dictionary(body)
var token := str(response.get("accessToken", ""))
+54
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
uid://cnqc7wymmiwdd