refactor: harden client runtime and exports
This commit is contained in:
@@ -18,32 +18,25 @@ signal profile_update_succeeded(profile: Dictionary)
|
||||
signal profile_update_failed(message: String)
|
||||
signal logout_completed()
|
||||
|
||||
const NetworkConfig = preload("res://_Core/utils/NetworkConfig.gd")
|
||||
const SecureSessionStore = preload("res://_Core/security/SecureSessionStore.gd")
|
||||
|
||||
const DEFAULT_AUTH_CONFIG_PATH: String = "user://auth.cfg"
|
||||
const REQUEST_TIMEOUT: float = 12.0
|
||||
const DEFAULT_AUTH_CONFIG_PATH: String = "user://auth.secure"
|
||||
const LEGACY_AUTH_CONFIG_PATH: String = "user://auth.cfg"
|
||||
|
||||
var _access_token: String = ""
|
||||
var _refresh_token: String = ""
|
||||
var _current_user: Dictionary = {}
|
||||
var _current_profile: Dictionary = {}
|
||||
var _active_requests: Array[HTTPRequest] = []
|
||||
var _session_generation: int = 0
|
||||
var _account_generation: int = 0
|
||||
var _refresh_in_flight: bool = false
|
||||
var _auth_config_path: String = DEFAULT_AUTH_CONFIG_PATH
|
||||
var _show_welcome_after_registration: bool = false
|
||||
var _secure_session_store: RefCounted = SecureSessionStore.new()
|
||||
|
||||
func _ready() -> void:
|
||||
_load_cached_session()
|
||||
|
||||
func _exit_tree() -> void:
|
||||
for request in _active_requests:
|
||||
if is_instance_valid(request):
|
||||
request.cancel_request()
|
||||
request.queue_free()
|
||||
_active_requests.clear()
|
||||
|
||||
func is_authenticated() -> bool:
|
||||
return not _access_token.strip_edges().is_empty()
|
||||
|
||||
@@ -213,69 +206,30 @@ func refresh_session() -> void:
|
||||
|
||||
func logout() -> void:
|
||||
_clear_session_memory()
|
||||
if FileAccess.file_exists(_auth_config_path):
|
||||
DirAccess.remove_absolute(_auth_config_path)
|
||||
_secure_session_store.call("clear", _auth_config_path)
|
||||
auth_state_changed.emit(false, {})
|
||||
_emit_event(EventNames.AUTH_LOGOUT, {})
|
||||
logout_completed.emit()
|
||||
call_deferred("_return_to_auth_scene")
|
||||
|
||||
func _request_json(endpoint: String, payload: Dictionary, callback: Callable, method: int = HTTPClient.METHOD_POST, authenticated: bool = false, accountBound: bool = false) -> void:
|
||||
var request := HTTPRequest.new()
|
||||
request.timeout = REQUEST_TIMEOUT
|
||||
request.set_meta("account_bound", accountBound)
|
||||
add_child(request)
|
||||
_active_requests.append(request)
|
||||
var requestGeneration := _account_generation
|
||||
|
||||
request.request_completed.connect(func(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
_active_requests.erase(request)
|
||||
request.queue_free()
|
||||
if accountBound and requestGeneration != _account_generation:
|
||||
return
|
||||
_handle_json_response(result, response_code, body, callback)
|
||||
var api_client := get_node_or_null("/root/ApiClient")
|
||||
if api_client == null or not api_client.has_method("request_json"):
|
||||
callback.call(false, {}, {"message": "ApiClient 不可用"})
|
||||
return
|
||||
var request_generation := _account_generation
|
||||
api_client.call(
|
||||
"request_json",
|
||||
endpoint,
|
||||
payload,
|
||||
func(success: bool, response: Dictionary, error_info: Dictionary) -> void:
|
||||
if accountBound and request_generation != _account_generation:
|
||||
return
|
||||
callback.call(success, response, error_info),
|
||||
method,
|
||||
authenticated
|
||||
)
|
||||
|
||||
var url := "%s%s" % [NetworkConfig.get_api_base_url(), endpoint]
|
||||
var headers := PackedStringArray(["Content-Type: application/json"])
|
||||
if authenticated and not _access_token.strip_edges().is_empty():
|
||||
headers.append("Authorization: Bearer %s" % _access_token)
|
||||
var body := "" if method == HTTPClient.METHOD_GET else JSON.stringify(payload)
|
||||
var err := request.request(url, headers, method, body)
|
||||
if err != OK:
|
||||
_active_requests.erase(request)
|
||||
request.queue_free()
|
||||
if not accountBound or requestGeneration == _account_generation:
|
||||
callback.call(false, {}, {"message": "网络请求发送失败: %s" % error_string(err)})
|
||||
|
||||
func _handle_json_response(result: int, response_code: int, body: PackedByteArray, callback: Callable) -> void:
|
||||
var body_text := body.get_string_from_utf8()
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
callback.call(false, {}, {"message": "网络请求失败: %s" % _http_result_to_string(result)})
|
||||
return
|
||||
|
||||
var json := JSON.new()
|
||||
if json.parse(body_text) != OK:
|
||||
callback.call(false, {}, {"message": "服务器响应解析失败"})
|
||||
return
|
||||
|
||||
var payload_variant: Variant = json.data
|
||||
if not (payload_variant is Dictionary):
|
||||
callback.call(false, {}, {"message": "服务器响应格式错误"})
|
||||
return
|
||||
|
||||
var response: Dictionary = payload_variant
|
||||
var success := response_code >= 200 and response_code < 300 and bool(response.get("success", true))
|
||||
if success:
|
||||
callback.call(true, response, {})
|
||||
return
|
||||
|
||||
callback.call(false, response, {
|
||||
"message": str(response.get("message", "请求失败")),
|
||||
"response_code": response_code,
|
||||
"error_code": str(response.get("error_code", ""))
|
||||
})
|
||||
|
||||
func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary) -> void:
|
||||
if not success:
|
||||
login_failed.emit(str(error_info.get("message", "登录失败")))
|
||||
@@ -415,35 +369,34 @@ func _refresh_player_snapshot() -> void:
|
||||
playerStateManager.call_deferred("refresh_snapshot")
|
||||
|
||||
func _save_cached_session() -> void:
|
||||
var config := ConfigFile.new()
|
||||
config.set_value("auth", "refresh_token", _refresh_token)
|
||||
config.set_value("auth", "access_token", _access_token)
|
||||
config.set_value("auth", "saved_at", Time.get_unix_time_from_system())
|
||||
var err := config.save(_auth_config_path)
|
||||
if err != OK:
|
||||
push_warning("AuthManager: 保存本地登录状态失败: %s" % error_string(err))
|
||||
if _refresh_token.is_empty():
|
||||
_secure_session_store.call("clear", _auth_config_path)
|
||||
return
|
||||
if not bool(_secure_session_store.call("save_refresh_token", _auth_config_path, _refresh_token)):
|
||||
push_warning("AuthManager: 当前平台不支持安全持久化,将仅保留本次会话")
|
||||
|
||||
func _load_cached_session(emit_cached_state: bool = true) -> void:
|
||||
if not FileAccess.file_exists(_auth_config_path):
|
||||
return
|
||||
|
||||
var config := ConfigFile.new()
|
||||
if config.load(_auth_config_path) != OK:
|
||||
return
|
||||
|
||||
_refresh_token = str(config.get_value("auth", "refresh_token", "")).strip_edges()
|
||||
_access_token = str(config.get_value("auth", "access_token", "")).strip_edges()
|
||||
_migrate_legacy_cached_session()
|
||||
_refresh_token = str(_secure_session_store.call("load_refresh_token", _auth_config_path)).strip_edges()
|
||||
_access_token = ""
|
||||
_current_user.clear()
|
||||
_current_profile.clear()
|
||||
if _refresh_token.is_empty():
|
||||
return
|
||||
_session_generation += 1
|
||||
_account_generation += 1
|
||||
if emit_cached_state:
|
||||
call_deferred("refresh_session")
|
||||
|
||||
if is_authenticated():
|
||||
_session_generation += 1
|
||||
_account_generation += 1
|
||||
if emit_cached_state and is_authenticated():
|
||||
call_deferred("_emit_cached_auth_state")
|
||||
|
||||
func _emit_cached_auth_state() -> void:
|
||||
auth_state_changed.emit(true, get_current_user())
|
||||
func _migrate_legacy_cached_session() -> void:
|
||||
if not FileAccess.file_exists(LEGACY_AUTH_CONFIG_PATH):
|
||||
return
|
||||
var config := ConfigFile.new()
|
||||
if config.load(LEGACY_AUTH_CONFIG_PATH) == OK:
|
||||
var legacy_refresh_token := str(config.get_value("auth", "refresh_token", "")).strip_edges()
|
||||
if not legacy_refresh_token.is_empty():
|
||||
_secure_session_store.call("save_refresh_token", _auth_config_path, legacy_refresh_token)
|
||||
DirAccess.remove_absolute(ProjectSettings.globalize_path(LEGACY_AUTH_CONFIG_PATH))
|
||||
|
||||
func _clear_session_memory() -> void:
|
||||
_access_token = ""
|
||||
@@ -456,11 +409,6 @@ func _advance_account_generation() -> void:
|
||||
_session_generation += 1
|
||||
_account_generation += 1
|
||||
_refresh_in_flight = false
|
||||
for request in _active_requests.duplicate():
|
||||
if is_instance_valid(request) and bool(request.get_meta("account_bound", false)):
|
||||
request.cancel_request()
|
||||
request.queue_free()
|
||||
_active_requests.erase(request)
|
||||
|
||||
func _return_to_auth_scene() -> void:
|
||||
var tree := get_tree()
|
||||
@@ -503,20 +451,3 @@ func _password_has_letter_and_number(password: String) -> bool:
|
||||
if code >= 48 and code <= 57:
|
||||
has_number = true
|
||||
return has_letter and has_number
|
||||
|
||||
func _http_result_to_string(result: int) -> String:
|
||||
match result:
|
||||
HTTPRequest.RESULT_SUCCESS:
|
||||
return "SUCCESS"
|
||||
HTTPRequest.RESULT_TIMEOUT:
|
||||
return "TIMEOUT"
|
||||
HTTPRequest.RESULT_CANT_CONNECT:
|
||||
return "CANT_CONNECT"
|
||||
HTTPRequest.RESULT_CANT_RESOLVE:
|
||||
return "CANT_RESOLVE"
|
||||
HTTPRequest.RESULT_CONNECTION_ERROR:
|
||||
return "CONNECTION_ERROR"
|
||||
HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR:
|
||||
return "TLS_HANDSHAKE_ERROR"
|
||||
_:
|
||||
return "UNKNOWN_%d" % result
|
||||
|
||||
Reference in New Issue
Block a user