using System; using System.Collections; using System.Text; using UnityEngine; using UnityEngine.Networking; namespace BlockSpace.Worlds { public sealed class WorldBackendClient { [Serializable] public sealed class ResolveWorldResponse { public bool ok; public bool exists; public string worldId; public string name; public string visibility; public bool isCreator; public string updatedAt; public string error; } [Serializable] public sealed class LobbyTargetResponse { public bool ok; public string worldName; public string worldId; public string worldVersion; public string roomName; public string action; public bool exists; public int roomsSeen; public string error; } [Serializable] public sealed class VisitResponse { public bool ok; public int visits; public string error; } [Serializable] public sealed class BroadcastReloadResponse { public bool ok; public int targets; public string error; } [Serializable] public sealed class UploadGroupsResponse { public bool ok; public bool saved; public int bytes; public string updatedAt; public string error; } [Serializable] private sealed class BroadcastReloadRequest { public string playerId; public string worldName; } private readonly string baseUrl; public WorldBackendClient(string baseUrl) { this.baseUrl = (baseUrl ?? string.Empty).Trim().TrimEnd('/'); } public IEnumerator ResolveLobbyTarget(string worldName, int maxPlayers, Action onDone) { if (string.IsNullOrEmpty(baseUrl)) { onDone?.Invoke(new LobbyTargetResponse { ok = false, error = "No base URL set." }); yield break; } string text = (worldName ?? string.Empty).Trim(); if (string.IsNullOrEmpty(text)) { onDone?.Invoke(new LobbyTargetResponse { ok = false, error = "World name is required." }); yield break; } string uri = baseUrl + "/api/worlds/lobby-target?name=" + UnityWebRequest.EscapeURL(text) + "&maxPlayers=" + Mathf.Clamp(maxPlayers, 1, 255); using UnityWebRequest request = UnityWebRequest.Get(uri); request.downloadHandler = new DownloadHandlerBuffer(); yield return request.SendWebRequest(); LobbyTargetResponse lobbyTargetResponse = null; if (request.result == UnityWebRequest.Result.Success) { try { lobbyTargetResponse = JsonUtility.FromJson(request.downloadHandler.text); } catch { } } if (lobbyTargetResponse == null) { lobbyTargetResponse = new LobbyTargetResponse { ok = false, error = request.error }; } onDone?.Invoke(lobbyTargetResponse); } public IEnumerator ResolveWorld(string roomName, string playerId, Action onDone) { if (string.IsNullOrEmpty(baseUrl)) { onDone?.Invoke(new ResolveWorldResponse { ok = false, exists = false, error = "No base URL set." }); yield break; } string text = (roomName ?? string.Empty).Trim(); if (string.IsNullOrEmpty(text)) { onDone?.Invoke(new ResolveWorldResponse { ok = false, exists = false, error = "Room name is required." }); yield break; } string uri = baseUrl + "/api/worlds/resolve?name=" + UnityWebRequest.EscapeURL(text) + "&playerId=" + UnityWebRequest.EscapeURL(playerId ?? string.Empty); using UnityWebRequest request = UnityWebRequest.Get(uri); request.downloadHandler = new DownloadHandlerBuffer(); yield return request.SendWebRequest(); ResolveWorldResponse resolveWorldResponse = null; if (request.result == UnityWebRequest.Result.Success) { try { resolveWorldResponse = JsonUtility.FromJson(request.downloadHandler.text); } catch { } } if (resolveWorldResponse == null) { resolveWorldResponse = new ResolveWorldResponse { ok = false, exists = false, error = request.error }; } onDone?.Invoke(resolveWorldResponse); } public IEnumerator DownloadGroups(string worldId, Action onDone) { if (string.IsNullOrEmpty(baseUrl)) { onDone?.Invoke(Array.Empty()); yield break; } string text = (worldId ?? string.Empty).Trim(); if (string.IsNullOrEmpty(text)) { onDone?.Invoke(Array.Empty()); yield break; } string uri = baseUrl + "/api/worlds/" + UnityWebRequest.EscapeURL(text) + "/groups"; using UnityWebRequest request = UnityWebRequest.Get(uri); request.downloadHandler = new DownloadHandlerBuffer(); yield return request.SendWebRequest(); if (request.result != UnityWebRequest.Result.Success) { onDone?.Invoke(Array.Empty()); yield break; } onDone?.Invoke(request.downloadHandler.data ?? Array.Empty()); } public IEnumerator UploadGroups(string worldId, byte[] gzipBytes, string playerId, Action onDone) { if (string.IsNullOrEmpty(baseUrl)) { onDone?.Invoke(new UploadGroupsResponse { ok = false, saved = false, bytes = 0, error = "No base URL set." }); yield break; } string text = (worldId ?? string.Empty).Trim(); if (string.IsNullOrEmpty(text)) { onDone?.Invoke(new UploadGroupsResponse { ok = false, saved = false, bytes = 0, error = "worldId is required." }); yield break; } string text2 = (playerId ?? string.Empty).Trim(); if (string.IsNullOrEmpty(text2)) { onDone?.Invoke(new UploadGroupsResponse { ok = false, saved = false, bytes = 0, error = "playerId is required." }); yield break; } string url = baseUrl + "/api/worlds/" + UnityWebRequest.EscapeURL(text) + "/groups?playerId=" + UnityWebRequest.EscapeURL(text2); byte[] data = gzipBytes ?? Array.Empty(); using UnityWebRequest request = new UnityWebRequest(url, "PUT"); request.uploadHandler = new UploadHandlerRaw(data); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/octet-stream"); yield return request.SendWebRequest(); UploadGroupsResponse uploadGroupsResponse = null; if (request.result == UnityWebRequest.Result.Success) { try { uploadGroupsResponse = JsonUtility.FromJson(request.downloadHandler.text); } catch { } } if (uploadGroupsResponse == null) { uploadGroupsResponse = new UploadGroupsResponse { ok = false, saved = false, bytes = 0, error = request.error }; } onDone?.Invoke(uploadGroupsResponse); } public IEnumerator IncrementVisit(string worldId, Action onDone) { if (string.IsNullOrEmpty(baseUrl)) { onDone?.Invoke(new VisitResponse { ok = false, visits = 0, error = "No base URL set." }); yield break; } string text = (worldId ?? string.Empty).Trim(); if (string.IsNullOrEmpty(text)) { onDone?.Invoke(new VisitResponse { ok = false, visits = 0, error = "worldId is required." }); yield break; } string url = baseUrl + "/api/worlds/" + UnityWebRequest.EscapeURL(text) + "/visit"; using UnityWebRequest request = new UnityWebRequest(url, "POST"); request.uploadHandler = new UploadHandlerRaw(Array.Empty()); request.downloadHandler = new DownloadHandlerBuffer(); yield return request.SendWebRequest(); VisitResponse visitResponse = null; if (request.result == UnityWebRequest.Result.Success) { try { visitResponse = JsonUtility.FromJson(request.downloadHandler.text); } catch { } } if (visitResponse == null) { visitResponse = new VisitResponse { ok = false, visits = 0, error = request.error }; } onDone?.Invoke(visitResponse); } public IEnumerator BroadcastReload(string playerId, string worldName, Action onDone) { if (string.IsNullOrEmpty(baseUrl)) { onDone?.Invoke(new BroadcastReloadResponse { ok = false, targets = 0, error = "No base URL set." }); yield break; } string url = baseUrl + "/api/worlds/broadcast-reload"; string s = JsonUtility.ToJson(new BroadcastReloadRequest { playerId = (playerId ?? ""), worldName = (worldName ?? "") }); using UnityWebRequest request = new UnityWebRequest(url, "POST"); byte[] bytes = Encoding.UTF8.GetBytes(s); request.uploadHandler = new UploadHandlerRaw(bytes); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); yield return request.SendWebRequest(); BroadcastReloadResponse broadcastReloadResponse = null; if (request.result == UnityWebRequest.Result.Success) { try { broadcastReloadResponse = JsonUtility.FromJson(request.downloadHandler.text); } catch { } } if (broadcastReloadResponse == null) { broadcastReloadResponse = new BroadcastReloadResponse { ok = false, targets = 0, error = request.error }; } onDone?.Invoke(broadcastReloadResponse); } } }