using System;
using System.Collections;
using AOT.Boot;
using UnityEngine;
namespace HotUpdate.Net
{
///
/// 登录 → WS 连接编排。
///
public sealed class GameNet : MonoBehaviour
{
public string HttpBase = AuthApi.DefaultHttpBase;
public string WsUrlTemplate = "wss://game.xworld.ren/ws?pid={pid}&token={token}";
public SessionData Session { get; private set; }
public WsClient Socket { get; private set; }
public string Status { get; private set; } = "idle";
public event Action OnAuthed;
public event Action OnWsReady;
public event Action OnStatus;
public event Action OnWsMessage;
BootContext _boot;
bool _started;
public void Bind(BootContext boot)
{
_boot = boot;
if (_boot == null) return;
if (!string.IsNullOrEmpty(_boot.HttpBase))
HttpBase = _boot.HttpBase;
if (!string.IsNullOrEmpty(_boot.WsUrlTemplate))
{
// 旧模板只有 pid 时补上 token
var tpl = _boot.WsUrlTemplate;
if (!tpl.Contains("{token}"))
{
tpl = tpl.Contains("?")
? tpl + (tpl.EndsWith("?") || tpl.EndsWith("&") ? "token={token}" : "&token={token}")
: tpl + "?pid={pid}&token={token}";
}
WsUrlTemplate = tpl;
}
}
public void StartGuestSession()
{
if (_started) return;
_started = true;
StartCoroutine(RunGuestFlow());
}
IEnumerator RunGuestFlow()
{
SetStatus("auth…");
var deviceId = DeviceId.GetOrCreate();
// 服务器要求 username/password;编辑器自动账号
var username = $"guest_{deviceId}";
var password = $"pw_{deviceId}";
AuthResponse auth = null;
string err = null;
yield return AuthApi.EnsureAccount(HttpBase, username, password, (r, e) => { auth = r; err = e; });
if (auth == null || string.IsNullOrEmpty(auth.token))
{
SetStatus($"auth fail: {err}");
Debug.LogError($"[GameNet] auth failed: {err}");
yield break;
}
Session = new SessionData
{
Username = username,
Token = auth.token,
Pid = auth.pid,
DeviceId = deviceId,
};
Debug.Log($"[GameNet] authed pid={Session.Pid} user={username}");
OnAuthed?.Invoke(Session);
SetStatus($"authed pid={Session.Pid}");
yield return ConnectWs();
}
IEnumerator ConnectWs()
{
if (Session == null || !Session.IsValid)
{
SetStatus("no session");
yield break;
}
SetStatus("ws connecting…");
Socket?.Dispose();
Socket = new WsClient();
Socket.AutoReconnect = true;
Socket.OnConnected += () =>
{
SetStatus("ws connected");
OnWsReady?.Invoke();
// 应用层握手(服务端当前可无响应,保留扩展)
Socket.SendText($"{{\"op\":\"hello\",\"proto\":1,\"app\":\"{_boot?.AppVersion}\",\"res\":\"{_boot?.ResVersion}\",\"pid\":{Session.Pid}}}");
};
Socket.OnDisconnected += reason => SetStatus($"ws down: {reason}");
Socket.OnMessage += msg =>
{
Debug.Log($"[GameNet] << {msg}");
OnWsMessage?.Invoke(msg);
};
var url = Session.BuildWsUrl(WsUrlTemplate);
Socket.Connect(url);
// 等待最多 15s
float t = 0f;
while (t < 15f && Socket.State != WsState.Connected && Socket.State != WsState.Disconnected)
{
t += Time.unscaledDeltaTime;
yield return null;
}
if (Socket.State == WsState.Connected)
SetStatus("ws connected");
else if (Socket.State == WsState.Disconnected && !string.IsNullOrEmpty(Socket.LastError))
SetStatus($"ws fail: {Socket.LastError}");
else if (Socket.State != WsState.Connected)
SetStatus($"ws state={Socket.State}");
}
void Update()
{
Socket?.Tick();
}
void OnDestroy()
{
Socket?.Dispose();
Socket = null;
}
void SetStatus(string s)
{
Status = s;
Debug.Log($"[GameNet] {s}");
OnStatus?.Invoke(s);
}
}
}