extends SceneTree
## LIVE probe for IdentityFlow (hyper-games #89): a REAL keet-daemon on scratch
## storage over the TCP transport (Windows' path), driven through a real
## KeetUdsClient — first run (name + vault), recovery phrase, restore.
##
## Needs a scratch daemon (see the #89 comment for the harness) and:
## HYPERTPS_DAEMON_TCP=127.0.0.1:<port> HYPERTPS_DAEMON_TOKEN=<token>
## LIVE_RESTORE_PHRASE=<mnemonic> LIVE_RESTORE_MEMBER_ID=<its z32 memberId>
## NEVER point it at a real identity's daemon: step 4 switches identity.
const IdentityFlow := preload("res://scenes/game/identity_flow.gd")
var ok := true
var _client: Node
var _flow: Node
var _reply: Variant = null
var _err: Variant = null
var _got := false
func _p(c: bool, m: String) -> void:
print(("ok " if c else "FAIL: ") + m)
if not c:
ok = false
func _init() -> void:
_main.call_deferred()
func _on_reply(result: Variant, err: Variant) -> void:
_reply = result
_err = err
_got = true
func _rpc(method: String, params: Dictionary = {}) -> Variant:
_got = false
_client.call_rpc(method, params, _on_reply, 180.0)
while not _got:
await process_frame
return _reply
func _wait_until(cond: Callable, seconds: float) -> bool:
var end := Time.get_ticks_msec() + int(seconds * 1000.0)
while Time.get_ticks_msec() < end:
if cond.call():
return true
await process_frame
return cond.call()
func _form_closed() -> bool:
return _flow.form.is_empty()
func _form_has_ask() -> bool:
return _flow.form.has("ask")
func _form_shown() -> bool:
return _flow.form.get("shown", false)
func _connected() -> bool:
return bool(_client.get("is_connected"))
func _main() -> void:
_client = KeetUdsClient.new()
root.add_child(_client)
_p(await _wait_until(_connected, 20.0), "connected to the scratch daemon over TCP")
_flow = IdentityFlow.new()
_flow.client = _client
_flow.interactive = true
root.add_child(_flow)
# 1. First run.
var before: Dictionary = await _rpc("identity")
var member_before := str(before.get("memberId", ""))
_p(member_before != "" and before.get("displayName") in [null, ""], "fresh identity %s has no name (displayName=%s)" % [member_before, str(before.get("displayName"))])
_flow.check_first_run(before)
_p(await _wait_until(func(): return _flow.form.get("kind", "") == "first_run", 20.0), "first-run step open")
_flow.form.inputs.name.text = "livetester"
_flow.form.inputs.password.text = "live password 1"
_flow.form.inputs.confirm.text = "live password 1"
_flow.form.dlg.confirmed.emit()
_p(await _wait_until(_form_closed, 200.0), "first run completed (status: %s)" % ("" if _flow.form.is_empty() else _flow.form.status.text))
var after: Dictionary = await _rpc("identity")
_p(str(after.get("displayName", "")) == "livetester", "keet now says displayName=livetester (got %s)" % str(after.get("displayName")))
_p(str(after.get("memberId", "")) == member_before, "same identity after the vault backup (%s)" % str(after.get("memberId")))
var st: Dictionary = await _rpc("vault.status")
_p(bool(st.get("exists", false)), "vault written")
# 2. Recovery phrase.
_flow.show_phrase()
_flow.form.inputs.password.text = "live password 1"
_flow.form.dlg.confirmed.emit()
_p(await _wait_until(_form_shown, 120.0), "phrase shown")
var words: String = _flow.form.inputs.phrase.text if _flow.form.inputs.has("phrase") else ""
_p(words.split(" ").size() == 24, "24 words (%d)" % words.split(" ").size())
_flow.form.dlg.confirmed.emit()
# 3. Wrong password says so.
_flow.show_phrase()
_flow.form.inputs.password.text = "not the password"
_flow.form.dlg.confirmed.emit()
_p(await _wait_until(func(): return _flow.form.status.text == "Wrong password.", 120.0), "wrong password refused: %s" % _flow.form.status.text)
_flow._close()
# 4. Restore.
var phrase := OS.get_environment("LIVE_RESTORE_PHRASE")
var expect := OS.get_environment("LIVE_RESTORE_MEMBER_ID")
_flow.restore()
_flow.form.inputs.phrase.text = phrase
_flow.form.inputs.password.text = "live password 2"
_flow.form.inputs.confirm.text = "live password 2"
_flow.form.dlg.confirmed.emit()
_p(await _wait_until(_form_has_ask, 5.0), "switch confirmation asked")
_flow.form.ask.confirmed.emit()
_p(await _wait_until(_form_closed, 200.0), "restore completed (status: %s)" % ("" if _flow.form.is_empty() else _flow.form.status.text))
# The swap drops the connection's subscriptions; give the client a moment.
await _wait_until(_connected, 20.0)
var restored: Dictionary = await _rpc("identity")
_p(str(restored.get("memberId", "")) == expect, "identity is now the phrase's (%s, expected %s)" % [str(restored.get("memberId")), expect])
var list: Variant = await _rpc("identity-manager.list")
var ids: Array = list.get("identities", []) if list is Dictionary else []
_p(ids.size() == 2, "the original identity is still on this device (%d identities)" % ids.size())
print("PROBE identity_flow_live: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1)