231 lines
8.3 KiB
GDScript
231 lines
8.3 KiB
GDScript
extends Node
|
|
|
|
signal credits_changed
|
|
signal credits_failed(message: String)
|
|
signal store_operation_completed(message: String)
|
|
signal spin_resolved(reward_key: String, reward_label: String, coins_won: int)
|
|
signal spin_config_loaded
|
|
signal award_received(amount: int, reason: String)
|
|
signal ledger_changed
|
|
|
|
const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd")
|
|
const FeatureFlags := preload("res://scripts/config/feature_flags.gd")
|
|
const DEFAULT_URL := "https://us3q.ranaze.com/api/credits"
|
|
|
|
var credits: int = 0
|
|
var exam_tokens: int = 0
|
|
var qbank_reset_tokens: int = 0
|
|
var bank_cycle: int = 0
|
|
var products: Array = []
|
|
var spin_cost := 50
|
|
var spin_rewards: Array = []
|
|
var loaded := false
|
|
var loading := false
|
|
var request: HTTPRequest
|
|
var action_request: HTTPRequest
|
|
var spin_config_request: HTTPRequest
|
|
var ledger_request: HTTPRequest
|
|
var request_token := ""
|
|
var api_url := DEFAULT_URL
|
|
var pending_store_action := ""
|
|
var ledger: Array = []
|
|
var ledger_next_before := ""
|
|
var ledger_append := false
|
|
|
|
|
|
func _ready() -> void:
|
|
api_url = EndpointConfig.resolve(
|
|
str(ProjectSettings.get_setting("step3/credits_api_url", DEFAULT_URL)),
|
|
"/api/credits")
|
|
request = HTTPRequest.new()
|
|
request.timeout = 20.0
|
|
request.request_completed.connect(_on_completed)
|
|
add_child(request)
|
|
action_request = HTTPRequest.new()
|
|
action_request.timeout = 20.0
|
|
action_request.request_completed.connect(_on_action_completed)
|
|
add_child(action_request)
|
|
spin_config_request = HTTPRequest.new()
|
|
spin_config_request.timeout = 20.0
|
|
spin_config_request.request_completed.connect(_on_spin_config_completed)
|
|
add_child(spin_config_request)
|
|
ledger_request = HTTPRequest.new()
|
|
ledger_request.timeout = 20.0
|
|
ledger_request.request_completed.connect(_on_ledger_completed)
|
|
add_child(ledger_request)
|
|
AuthState.auth_changed.connect(_on_auth_changed)
|
|
|
|
|
|
func refresh() -> void:
|
|
if loading or not AuthState.can_send_authenticated_request():
|
|
return
|
|
loading = true
|
|
request_token = AuthState.access_token
|
|
var headers := PackedStringArray(["Authorization: Bearer %s" % request_token])
|
|
var error := request.request(api_url, headers, HTTPClient.METHOD_GET)
|
|
if error != OK:
|
|
loading = false
|
|
credits_failed.emit("Points balance could not be loaded. Check your connection and try again.")
|
|
|
|
|
|
func apply_status(parsed: Dictionary) -> void:
|
|
credits = int(parsed.get("credits", credits))
|
|
exam_tokens = int(parsed.get("examTokens", exam_tokens))
|
|
qbank_reset_tokens = int(parsed.get("qBankResetTokens", qbank_reset_tokens))
|
|
bank_cycle = int(parsed.get("bankCycle", bank_cycle))
|
|
products = parsed.get("products", products)
|
|
loaded = true
|
|
credits_changed.emit()
|
|
|
|
|
|
func apply_award_response(parsed: Dictionary) -> void:
|
|
var status: Variant = parsed.get("credits", null)
|
|
if typeof(status) == TYPE_DICTIONARY:
|
|
apply_status(status)
|
|
for award_variant in parsed.get("creditAwards", []):
|
|
if typeof(award_variant) != TYPE_DICTIONARY:
|
|
continue
|
|
var award: Dictionary = award_variant
|
|
var amount := int(award.get("amount", 0))
|
|
var reason_code := str(award.get("reasonCode", award.get("reason_code", "")))
|
|
if amount > 0 and (FeatureFlags.progression_enabled() or not FeatureFlags.is_progression_reward(reason_code)):
|
|
award_received.emit(amount, str(award.get("description", "Learning reward")))
|
|
|
|
|
|
func load_ledger(before: String = "") -> void:
|
|
if not AuthState.can_send_authenticated_request() or ledger_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
|
return
|
|
ledger_append = not before.is_empty()
|
|
var suffix := "/ledger?limit=20"
|
|
if not before.is_empty():
|
|
suffix += "&before=%s" % before.uri_encode()
|
|
ledger_request.request(api_url + suffix, PackedStringArray(["Authorization: Bearer %s" % AuthState.access_token]), HTTPClient.METHOD_GET)
|
|
|
|
|
|
func _on_ledger_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
|
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
|
credits_failed.emit("Coin history could not be loaded.")
|
|
return
|
|
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
|
if typeof(parsed) != TYPE_DICTIONARY:
|
|
return
|
|
var items: Array = parsed.get("items", [])
|
|
ledger = ledger + items if ledger_append else items
|
|
var cursor: Variant = parsed.get("nextBefore", null)
|
|
ledger_next_before = "" if cursor == null else str(cursor)
|
|
ledger_changed.emit()
|
|
|
|
|
|
func _on_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
|
loading = false
|
|
if response_code == 401:
|
|
AuthState.handle_unauthorized(request_token)
|
|
return
|
|
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
|
credits_failed.emit("Points balance could not be loaded. Check your connection and try again.")
|
|
return
|
|
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
|
if typeof(parsed) != TYPE_DICTIONARY:
|
|
credits_failed.emit("The credits service returned an invalid response.")
|
|
return
|
|
apply_status(parsed)
|
|
|
|
|
|
func purchase(sku: String) -> void:
|
|
pending_store_action = "purchase"
|
|
_post("/purchases", {"sku": sku, "operationId": _operation_id("purchase")})
|
|
|
|
|
|
func reset_qbank() -> void:
|
|
pending_store_action = "reset"
|
|
_post("/qbank-reset", {"confirmation": "RESET_QBANK", "operationId": _operation_id("reset")})
|
|
|
|
|
|
func spin() -> void:
|
|
pending_store_action = "spin"
|
|
_post("/spin", {"operationId": _operation_id("spin")})
|
|
|
|
|
|
func fetch_spin_config() -> void:
|
|
if not AuthState.can_send_authenticated_request():
|
|
return
|
|
var headers := PackedStringArray(["Authorization: Bearer %s" % AuthState.access_token])
|
|
spin_config_request.request(api_url + "/spin-config", headers, HTTPClient.METHOD_GET)
|
|
|
|
|
|
func _on_spin_config_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
|
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
|
return
|
|
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
|
if typeof(parsed) != TYPE_DICTIONARY:
|
|
return
|
|
spin_cost = int(parsed.get("spinCost", spin_cost))
|
|
spin_rewards = parsed.get("rewards", spin_rewards)
|
|
spin_config_loaded.emit()
|
|
|
|
|
|
func _post(path: String, payload: Dictionary) -> void:
|
|
if not AuthState.can_send_authenticated_request():
|
|
pending_store_action = ""
|
|
credits_failed.emit("You're signed out. Sign back in and try again.")
|
|
return
|
|
var headers := PackedStringArray([
|
|
"Authorization: Bearer %s" % AuthState.access_token,
|
|
"Content-Type: application/json"
|
|
])
|
|
var error := action_request.request(api_url + path, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
|
|
if error != OK:
|
|
credits_failed.emit("The Rz Coin Store could not complete that action.")
|
|
|
|
|
|
func _on_action_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
|
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
|
if result != HTTPRequest.RESULT_SUCCESS:
|
|
credits_failed.emit("The Rz Coin Store could not complete that action.")
|
|
return
|
|
if response_code >= 200 and response_code < 300 and typeof(parsed) == TYPE_DICTIONARY:
|
|
if pending_store_action == "spin":
|
|
var status: Variant = parsed.get("status", {})
|
|
if typeof(status) == TYPE_DICTIONARY:
|
|
apply_status(status)
|
|
pending_store_action = ""
|
|
spin_resolved.emit(
|
|
str(parsed.get("rewardKey", "")),
|
|
str(parsed.get("rewardLabel", "")),
|
|
int(parsed.get("coinsWon", 0)))
|
|
return
|
|
apply_status(parsed)
|
|
if pending_store_action == "reset":
|
|
ProfileService.reset_qbank_eligibility()
|
|
pending_store_action = ""
|
|
store_operation_completed.emit("Your Rz Coin Store balance was updated.")
|
|
return
|
|
if typeof(parsed) == TYPE_DICTIONARY:
|
|
var status: Variant = parsed.get("credits", {})
|
|
if typeof(status) == TYPE_DICTIONARY:
|
|
apply_status(status)
|
|
credits_failed.emit(str(parsed.get("message", "The store action could not be completed.")))
|
|
else:
|
|
credits_failed.emit("The store action could not be completed.")
|
|
pending_store_action = ""
|
|
|
|
|
|
func _operation_id(prefix: String) -> String:
|
|
return "%s:%s:%d" % [prefix, str(AuthState.username), Time.get_ticks_usec()]
|
|
|
|
|
|
func _on_auth_changed() -> void:
|
|
if not AuthState.is_logged_in:
|
|
credits = 0
|
|
exam_tokens = 0
|
|
qbank_reset_tokens = 0
|
|
bank_cycle = 0
|
|
products = []
|
|
spin_rewards = []
|
|
ledger = []
|
|
ledger_next_before = ""
|
|
loaded = false
|
|
loading = false
|
|
if request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
|
request.cancel_request()
|