You've already forked CC-Framework.BriskGameServer
Add package sync workflow
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public static class Brisk
|
||||
{
|
||||
private static BriskContext s_context;
|
||||
|
||||
static Brisk()
|
||||
{
|
||||
Auth = new BriskAuthModule();
|
||||
Player = new BriskPlayerModule();
|
||||
Config = new BriskConfigModule();
|
||||
Announcements = new BriskAnnouncementsModule();
|
||||
Leaderboard = new BriskLeaderboardModule();
|
||||
Archive = new BriskArchiveModule();
|
||||
Space = new BriskSpaceModule();
|
||||
}
|
||||
|
||||
public static event Action OnInitialized;
|
||||
public static event Action OnLoggedIn;
|
||||
public static event Action OnLoggedOut;
|
||||
public static event Action<BriskBlockingException> OnBlockingError;
|
||||
public static event Action<BriskAuthExpiredException> OnAuthExpired;
|
||||
|
||||
public static BriskAuthModule Auth { get; }
|
||||
|
||||
public static BriskPlayerModule Player { get; }
|
||||
|
||||
public static BriskConfigModule Config { get; }
|
||||
|
||||
public static BriskAnnouncementsModule Announcements { get; }
|
||||
|
||||
public static BriskLeaderboardModule Leaderboard { get; }
|
||||
|
||||
public static BriskArchiveModule Archive { get; }
|
||||
|
||||
public static BriskSpaceModule Space { get; }
|
||||
|
||||
public static bool IsInitialized => s_context != null;
|
||||
|
||||
public static bool IsLoggedIn => s_context != null && s_context.Session.HasAccessToken;
|
||||
|
||||
public static string AccessToken => s_context != null ? s_context.Session.AccessToken : null;
|
||||
|
||||
public static string PlayerId => s_context != null ? s_context.Session.PlayerId : null;
|
||||
|
||||
public static BriskIdentity Identity => s_context != null ? s_context.Session.Identity : null;
|
||||
|
||||
public static BriskOptions Options => s_context != null ? s_context.Options : null;
|
||||
|
||||
public static BriskBootstrapResult Bootstrap => s_context != null ? s_context.Bootstrap : null;
|
||||
|
||||
public static async Task InitializeAsync(BriskOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.Validate();
|
||||
var context = new BriskContext(options);
|
||||
s_context = context;
|
||||
|
||||
try
|
||||
{
|
||||
await BootstrapAsync(context);
|
||||
await RestoreSessionAsync(context);
|
||||
OnInitialized?.Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (context.Bootstrap == null)
|
||||
{
|
||||
s_context = null;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
s_context = null;
|
||||
}
|
||||
|
||||
public static void SetErrorPresenter(IBriskErrorPresenter presenter)
|
||||
{
|
||||
GetRequiredContext().ErrorPresenter = presenter ?? BriskDefaultErrorPresenter.Instance;
|
||||
}
|
||||
|
||||
public static void SetExitHandler(Action exitHandler)
|
||||
{
|
||||
GetRequiredContext().ExitHandler = exitHandler;
|
||||
}
|
||||
|
||||
internal static BriskContext GetRequiredContext()
|
||||
{
|
||||
if (s_context == null)
|
||||
{
|
||||
throw new BriskNotInitializedException();
|
||||
}
|
||||
|
||||
return s_context;
|
||||
}
|
||||
|
||||
internal static void NotifyLoggedIn()
|
||||
{
|
||||
OnLoggedIn?.Invoke();
|
||||
}
|
||||
|
||||
internal static void NotifyLoggedOut()
|
||||
{
|
||||
OnLoggedOut?.Invoke();
|
||||
}
|
||||
|
||||
internal static void NotifyBlockingError(BriskBlockingException exception)
|
||||
{
|
||||
s_context?.ErrorPresenter?.ShowBlockingError(exception);
|
||||
OnBlockingError?.Invoke(exception);
|
||||
}
|
||||
|
||||
internal static void NotifyAuthExpired(BriskAuthExpiredException exception)
|
||||
{
|
||||
s_context?.ErrorPresenter?.ShowAuthExpired(exception);
|
||||
OnAuthExpired?.Invoke(exception);
|
||||
}
|
||||
|
||||
private static async Task BootstrapAsync(BriskContext context)
|
||||
{
|
||||
var query = new Dictionary<string, string>
|
||||
{
|
||||
{ "game_key", context.Options.GameKey },
|
||||
{ "client_version", context.Options.ClientVersion },
|
||||
{ "device_id", context.Options.DeviceId }
|
||||
};
|
||||
|
||||
var bootstrapData = await context.HttpClient.GetDataAsync("/client/bootstrap", query, false);
|
||||
var bootstrap = BriskModelMapper.ToBootstrapResult(bootstrapData);
|
||||
context.Bootstrap = bootstrap;
|
||||
|
||||
if (bootstrap.MaintenanceMode)
|
||||
{
|
||||
var message = string.IsNullOrWhiteSpace(bootstrap.MaintenanceMessage)
|
||||
? "Server is under maintenance."
|
||||
: bootstrap.MaintenanceMessage;
|
||||
var exception = new BriskMaintenanceException(message);
|
||||
NotifyBlockingError(exception);
|
||||
throw exception;
|
||||
}
|
||||
|
||||
if (BriskVersionComparer.IsLessThan(context.Options.ClientVersion, bootstrap.MinClientVersion))
|
||||
{
|
||||
var exception = new BriskClientUpdateRequiredException("Client version is lower than the minimum supported version.");
|
||||
NotifyBlockingError(exception);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task RestoreSessionAsync(BriskContext context)
|
||||
{
|
||||
var storedSession = await context.TokenStore.LoadAsync();
|
||||
if (storedSession == null || string.IsNullOrWhiteSpace(storedSession.AccessToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.Session.Update(
|
||||
storedSession.AccessToken,
|
||||
storedSession.ExpiresAt,
|
||||
storedSession.PlayerId,
|
||||
storedSession.ProjectAccountId,
|
||||
storedSession.LoginProvider,
|
||||
storedSession.LoginUserId);
|
||||
|
||||
if (!context.Options.ValidateSessionOnInitialize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var meData = await context.HttpClient.GetDataAsync("/player/me", null, true);
|
||||
var me = BriskModelMapper.ToPlayerMe(meData);
|
||||
context.Session.Update(
|
||||
storedSession.AccessToken,
|
||||
storedSession.ExpiresAt,
|
||||
me.PlayerId,
|
||||
me.ProjectAccountId,
|
||||
me.LoginProvider,
|
||||
me.LoginUserId);
|
||||
}
|
||||
catch (BriskAuthExpiredException exception)
|
||||
{
|
||||
context.Session.Clear();
|
||||
await context.TokenStore.ClearAsync();
|
||||
NotifyAuthExpired(exception);
|
||||
}
|
||||
catch (BriskBlockingException exception)
|
||||
{
|
||||
NotifyBlockingError(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f175f0540648ffe43a061deb0d75159d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
public sealed class BriskBinaryResponse
|
||||
{
|
||||
public byte[] Bytes;
|
||||
public Dictionary<string, string> Headers;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 952c9679a69c1a740a55492f39acd66e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
public sealed class BriskContext
|
||||
{
|
||||
public BriskContext(BriskOptions options)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Session = new BriskSession();
|
||||
ErrorPresenter = options.ErrorPresenter ?? BriskDefaultErrorPresenter.Instance;
|
||||
ExitHandler = options.ExitHandler;
|
||||
TokenStore = options.TokenStore ?? new BriskPlayerPrefsTokenStore();
|
||||
HttpClient = new BriskHttpClient(this);
|
||||
}
|
||||
|
||||
public BriskOptions Options { get; }
|
||||
|
||||
public BriskSession Session { get; }
|
||||
|
||||
public BriskBootstrapResult Bootstrap { get; set; }
|
||||
|
||||
public IBriskTokenStore TokenStore { get; }
|
||||
|
||||
public BriskHttpClient HttpClient { get; }
|
||||
|
||||
public IBriskErrorPresenter ErrorPresenter { get; set; }
|
||||
|
||||
public Action ExitHandler { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca7516fa2069a024fa392f1c143a5a3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
||||
{
|
||||
private const string HostName = "BriskDefaultErrorDialog";
|
||||
|
||||
private static BriskDefaultErrorDialog s_instance;
|
||||
|
||||
private string _title;
|
||||
private string _message;
|
||||
private string _confirmText;
|
||||
private Action _onConfirm;
|
||||
private bool _visible;
|
||||
|
||||
public static void Show(string title, string message, string confirmText, Action onConfirm = null)
|
||||
{
|
||||
var host = EnsureInstance();
|
||||
host._title = string.IsNullOrWhiteSpace(title) ? "Brisk" : title;
|
||||
host._message = string.IsNullOrWhiteSpace(message) ? "Unknown error." : message;
|
||||
host._confirmText = string.IsNullOrWhiteSpace(confirmText) ? "OK" : confirmText;
|
||||
host._onConfirm = onConfirm;
|
||||
host._visible = true;
|
||||
}
|
||||
|
||||
private static BriskDefaultErrorDialog EnsureInstance()
|
||||
{
|
||||
if (s_instance != null)
|
||||
{
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
var existing = GameObject.Find(HostName);
|
||||
if (existing != null)
|
||||
{
|
||||
s_instance = existing.GetComponent<BriskDefaultErrorDialog>();
|
||||
if (s_instance != null)
|
||||
{
|
||||
return s_instance;
|
||||
}
|
||||
}
|
||||
|
||||
var gameObject = new GameObject(HostName);
|
||||
DontDestroyOnLoad(gameObject);
|
||||
s_instance = gameObject.AddComponent<BriskDefaultErrorDialog>();
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!_visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var overlayRect = new Rect(0, 0, Screen.width, Screen.height);
|
||||
var previousColor = GUI.color;
|
||||
GUI.color = new Color(0f, 0f, 0f, 0.65f);
|
||||
GUI.Box(overlayRect, GUIContent.none);
|
||||
GUI.color = previousColor;
|
||||
|
||||
var width = Mathf.Min(460f, Screen.width - 40f);
|
||||
var height = 220f;
|
||||
var dialogRect = new Rect(
|
||||
(Screen.width - width) * 0.5f,
|
||||
(Screen.height - height) * 0.5f,
|
||||
width,
|
||||
height);
|
||||
|
||||
GUILayout.BeginArea(dialogRect, GUI.skin.window);
|
||||
GUILayout.Space(8f);
|
||||
GUILayout.Label(_title, GUI.skin.label);
|
||||
GUILayout.Space(12f);
|
||||
GUILayout.Label(_message, GUI.skin.label);
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button(_confirmText, GUILayout.Height(36f)))
|
||||
{
|
||||
var callback = _onConfirm;
|
||||
_visible = false;
|
||||
_onConfirm = null;
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
GUILayout.Space(8f);
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95a9fe204ba119d4b8d7a296a7bd429b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class BriskDefaultErrorPresenter : IBriskErrorPresenter
|
||||
{
|
||||
private static readonly BriskDefaultErrorPresenter InstanceValue = new BriskDefaultErrorPresenter();
|
||||
|
||||
private BriskDefaultErrorPresenter()
|
||||
{
|
||||
}
|
||||
|
||||
public static BriskDefaultErrorPresenter Instance => InstanceValue;
|
||||
|
||||
public void ShowBlockingError(BriskBlockingException exception)
|
||||
{
|
||||
var message = exception != null && !string.IsNullOrWhiteSpace(exception.Message)
|
||||
? exception.Message
|
||||
: "A blocking error occurred.";
|
||||
|
||||
BriskDefaultErrorDialog.Show(
|
||||
title: GetBlockingTitle(exception),
|
||||
message: message,
|
||||
confirmText: "OK",
|
||||
onConfirm: () =>
|
||||
{
|
||||
var exitHandler = Brisk.IsInitialized ? Brisk.Options?.ExitHandler : null;
|
||||
exitHandler?.Invoke();
|
||||
});
|
||||
|
||||
Debug.LogError("[Brisk] Blocking error: " + message);
|
||||
}
|
||||
|
||||
public void ShowAuthExpired(BriskAuthExpiredException exception)
|
||||
{
|
||||
var message = exception != null && !string.IsNullOrWhiteSpace(exception.Message)
|
||||
? exception.Message
|
||||
: "Login expired. Please sign in again.";
|
||||
|
||||
BriskDefaultErrorDialog.Show(
|
||||
title: "Login Expired",
|
||||
message: message,
|
||||
confirmText: "OK");
|
||||
|
||||
Debug.LogWarning("[Brisk] Auth expired: " + message);
|
||||
}
|
||||
|
||||
private static string GetBlockingTitle(BriskBlockingException exception)
|
||||
{
|
||||
if (exception is BriskMaintenanceException)
|
||||
{
|
||||
return "Maintenance";
|
||||
}
|
||||
|
||||
if (exception is BriskAccountBannedException)
|
||||
{
|
||||
return "Account Restricted";
|
||||
}
|
||||
|
||||
if (exception is BriskClientUpdateRequiredException)
|
||||
{
|
||||
return "Update Required";
|
||||
}
|
||||
|
||||
return "Service Unavailable";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 681035ac4d9bc8147ad8ae630b131185
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
public static class BriskErrorClassifier
|
||||
{
|
||||
public static BriskException Classify(int httpStatus, int code, string message)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case 10001:
|
||||
case 10002:
|
||||
case 10023:
|
||||
return new BriskAuthExpiredException(message, code);
|
||||
case 10003:
|
||||
case 10025:
|
||||
return new BriskMaintenanceException(message, code);
|
||||
case 10004:
|
||||
case 10026:
|
||||
return new BriskAccountBannedException(message, code);
|
||||
default:
|
||||
return new BriskApiException(httpStatus, code, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41bc55282a88d6d4aaee810c79f56415
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
public sealed class BriskHttpClient
|
||||
{
|
||||
private readonly BriskContext _context;
|
||||
|
||||
public BriskHttpClient(BriskContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>> GetDataAsync(string path, Dictionary<string, string> query = null, bool auth = false)
|
||||
{
|
||||
return SendForDictionaryAsync(UnityWebRequest.kHttpVerbGET, path, query, null, auth);
|
||||
}
|
||||
|
||||
public Task<object> GetRawDataAsync(string path, Dictionary<string, string> query = null, bool auth = false)
|
||||
{
|
||||
return SendRawJsonAsync(UnityWebRequest.kHttpVerbGET, path, query, null, auth);
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>> PostJsonAsync(string path, object body, bool auth = false)
|
||||
{
|
||||
return SendForDictionaryAsync(UnityWebRequest.kHttpVerbPOST, path, null, body, auth);
|
||||
}
|
||||
|
||||
public Task<object> PostJsonRawAsync(string path, object body, bool auth = false)
|
||||
{
|
||||
return SendRawJsonAsync(UnityWebRequest.kHttpVerbPOST, path, null, body, auth);
|
||||
}
|
||||
|
||||
public Task<object> PostJsonRawAsync(string path, object body, bool auth, Dictionary<string, string> query)
|
||||
{
|
||||
return SendRawJsonAsync(UnityWebRequest.kHttpVerbPOST, path, query, body, auth);
|
||||
}
|
||||
|
||||
public Task<object> SendPutJsonRawAsync(string path, object body, bool auth = false)
|
||||
{
|
||||
return SendRawJsonAsync(UnityWebRequest.kHttpVerbPUT, path, null, body, auth);
|
||||
}
|
||||
|
||||
public Task<object> SendDeleteJsonRawAsync(string path, Dictionary<string, string> query = null, bool auth = false)
|
||||
{
|
||||
return SendRawJsonAsync(UnityWebRequest.kHttpVerbDELETE, path, query, null, auth);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, object>> PostMultipartAsync(string path, List<IMultipartFormSection> formSections, bool auth = false)
|
||||
{
|
||||
using (var request = UnityWebRequest.Post(BuildUrl(path, null), formSections))
|
||||
{
|
||||
if (auth)
|
||||
{
|
||||
AddAuthorizationHeader(request);
|
||||
}
|
||||
|
||||
request.SetRequestHeader("Accept", "application/json");
|
||||
await SendRequestAsync(request);
|
||||
return ParseEnvelope(request) as Dictionary<string, object> ?? new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BriskBinaryResponse> GetBytesAsync(string path, Dictionary<string, string> query = null, bool auth = false)
|
||||
{
|
||||
using (var request = BuildRequest(UnityWebRequest.kHttpVerbGET, path, query, null, auth))
|
||||
{
|
||||
await SendRequestAsync(request);
|
||||
EnsureSuccessOrThrow(request);
|
||||
return new BriskBinaryResponse
|
||||
{
|
||||
Bytes = request.downloadHandler != null ? request.downloadHandler.data : Array.Empty<byte>(),
|
||||
Headers = request.GetResponseHeaders() ?? new Dictionary<string, string>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, object>> SendForDictionaryAsync(string method, string path, Dictionary<string, string> query, object body, bool auth)
|
||||
{
|
||||
var data = await SendRawJsonAsync(method, path, query, body, auth);
|
||||
return data as Dictionary<string, object> ?? new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
private async Task<object> SendRawJsonAsync(string method, string path, Dictionary<string, string> query, object body, bool auth)
|
||||
{
|
||||
using (var request = BuildRequest(method, path, query, body, auth))
|
||||
{
|
||||
await SendRequestAsync(request);
|
||||
return ParseEnvelope(request);
|
||||
}
|
||||
}
|
||||
|
||||
private UnityWebRequest BuildRequest(string method, string path, Dictionary<string, string> query, object body, bool auth)
|
||||
{
|
||||
var url = BuildUrl(path, query);
|
||||
var request = new UnityWebRequest(url, method)
|
||||
{
|
||||
downloadHandler = new DownloadHandlerBuffer()
|
||||
};
|
||||
|
||||
request.SetRequestHeader("Accept", "application/json");
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
var json = BriskJson.Serialize(body);
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
request.uploadHandler = new UploadHandlerRaw(bytes);
|
||||
request.SetRequestHeader("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
if (auth)
|
||||
{
|
||||
AddAuthorizationHeader(request);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private async Task SendRequestAsync(UnityWebRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var operation = request.SendWebRequest();
|
||||
var taskSource = new TaskCompletionSource<bool>();
|
||||
operation.completed += _ => taskSource.TrySetResult(true);
|
||||
await taskSource.Task;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new BriskNetworkException("Failed to send request.", exception);
|
||||
}
|
||||
|
||||
if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.DataProcessingError)
|
||||
{
|
||||
throw new BriskNetworkException(request.error ?? "Network error.");
|
||||
}
|
||||
}
|
||||
|
||||
private object ParseEnvelope(UnityWebRequest request)
|
||||
{
|
||||
var httpStatus = (int)request.responseCode;
|
||||
var responseText = request.downloadHandler != null ? request.downloadHandler.text : null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(responseText))
|
||||
{
|
||||
if (httpStatus >= 200 && httpStatus < 300)
|
||||
{
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
throw new BriskHttpException(httpStatus, request.error ?? "HTTP error.");
|
||||
}
|
||||
|
||||
var payload = BriskJson.Deserialize(responseText) as Dictionary<string, object>;
|
||||
if (payload == null)
|
||||
{
|
||||
throw new BriskHttpException(httpStatus, "Invalid JSON response.");
|
||||
}
|
||||
|
||||
var code = BriskValueReader.GetInt(payload, "code");
|
||||
var message = BriskValueReader.GetString(payload, "message") ?? request.error ?? "Request failed.";
|
||||
if (code != 0 || httpStatus < 200 || httpStatus >= 300)
|
||||
{
|
||||
throw BriskErrorClassifier.Classify(httpStatus, code, message);
|
||||
}
|
||||
|
||||
if (BriskValueReader.TryGetValue(payload, "data", out var dataValue))
|
||||
{
|
||||
return dataValue;
|
||||
}
|
||||
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
private void EnsureSuccessOrThrow(UnityWebRequest request)
|
||||
{
|
||||
var httpStatus = (int)request.responseCode;
|
||||
if (httpStatus >= 200 && httpStatus < 300)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw CreateExceptionFromResponse(request);
|
||||
}
|
||||
|
||||
private BriskException CreateExceptionFromResponse(UnityWebRequest request)
|
||||
{
|
||||
var httpStatus = (int)request.responseCode;
|
||||
var responseText = request.downloadHandler != null ? request.downloadHandler.text : null;
|
||||
if (!string.IsNullOrWhiteSpace(responseText))
|
||||
{
|
||||
var payload = BriskJson.Deserialize(responseText) as Dictionary<string, object>;
|
||||
if (payload != null)
|
||||
{
|
||||
var code = BriskValueReader.GetInt(payload, "code");
|
||||
var message = BriskValueReader.GetString(payload, "message") ?? request.error ?? "Request failed.";
|
||||
return BriskErrorClassifier.Classify(httpStatus, code, message);
|
||||
}
|
||||
}
|
||||
|
||||
return new BriskHttpException(httpStatus, request.error ?? "HTTP error.");
|
||||
}
|
||||
|
||||
private void AddAuthorizationHeader(UnityWebRequest request)
|
||||
{
|
||||
var token = _context.Session.AccessToken;
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
throw new BriskAuthExpiredException("Missing access token.");
|
||||
}
|
||||
|
||||
request.SetRequestHeader("Authorization", "Bearer " + token);
|
||||
}
|
||||
|
||||
private string BuildUrl(string path, Dictionary<string, string> query)
|
||||
{
|
||||
var trimmedPath = path.StartsWith("/") ? path : "/" + path;
|
||||
var url = _context.Options.BaseUrl + trimmedPath;
|
||||
if (query == null || query.Count == 0)
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(url);
|
||||
var first = true;
|
||||
foreach (var pair in query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pair.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first)
|
||||
{
|
||||
builder.Append('?');
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append('&');
|
||||
}
|
||||
|
||||
builder.Append(UnityWebRequest.EscapeURL(pair.Key));
|
||||
builder.Append('=');
|
||||
builder.Append(UnityWebRequest.EscapeURL(pair.Value));
|
||||
first = false;
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a825891fadac404b97c755e6d60a3f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,443 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
internal static class BriskJson
|
||||
{
|
||||
public static object Deserialize(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Parser.Parse(json);
|
||||
}
|
||||
|
||||
public static string Serialize(object value)
|
||||
{
|
||||
return Serializer.Serialize(value);
|
||||
}
|
||||
|
||||
private sealed class Parser : IDisposable
|
||||
{
|
||||
private readonly StringReader _reader;
|
||||
|
||||
private Parser(string json)
|
||||
{
|
||||
_reader = new StringReader(json);
|
||||
}
|
||||
|
||||
public static object Parse(string json)
|
||||
{
|
||||
using (var parser = new Parser(json))
|
||||
{
|
||||
return parser.ParseValue();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_reader.Dispose();
|
||||
}
|
||||
|
||||
private object ParseValue()
|
||||
{
|
||||
EatWhitespace();
|
||||
if (_reader.Peek() == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (PeekChar)
|
||||
{
|
||||
case '{':
|
||||
return ParseObject();
|
||||
case '[':
|
||||
return ParseArray();
|
||||
case '"':
|
||||
return ParseString();
|
||||
case 't':
|
||||
return ParseTrue();
|
||||
case 'f':
|
||||
return ParseFalse();
|
||||
case 'n':
|
||||
return ParseNull();
|
||||
default:
|
||||
return ParseNumber();
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, object> ParseObject()
|
||||
{
|
||||
var table = new Dictionary<string, object>();
|
||||
ReadChar();
|
||||
while (true)
|
||||
{
|
||||
EatWhitespace();
|
||||
if (PeekChar == '}')
|
||||
{
|
||||
ReadChar();
|
||||
return table;
|
||||
}
|
||||
|
||||
var key = ParseString();
|
||||
EatWhitespace();
|
||||
ReadChar();
|
||||
var value = ParseValue();
|
||||
table[key] = value;
|
||||
EatWhitespace();
|
||||
if (PeekChar == ',')
|
||||
{
|
||||
ReadChar();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (PeekChar == '}')
|
||||
{
|
||||
ReadChar();
|
||||
return table;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<object> ParseArray()
|
||||
{
|
||||
var array = new List<object>();
|
||||
ReadChar();
|
||||
while (true)
|
||||
{
|
||||
EatWhitespace();
|
||||
if (PeekChar == ']')
|
||||
{
|
||||
ReadChar();
|
||||
return array;
|
||||
}
|
||||
|
||||
array.Add(ParseValue());
|
||||
EatWhitespace();
|
||||
if (PeekChar == ',')
|
||||
{
|
||||
ReadChar();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (PeekChar == ']')
|
||||
{
|
||||
ReadChar();
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ParseString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
ReadChar();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (_reader.Peek() == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var ch = ReadChar();
|
||||
if (ch == '"')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch == '\\')
|
||||
{
|
||||
if (_reader.Peek() == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ch = ReadChar();
|
||||
switch (ch)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
builder.Append(ch);
|
||||
break;
|
||||
case 'b':
|
||||
builder.Append('\b');
|
||||
break;
|
||||
case 'f':
|
||||
builder.Append('\f');
|
||||
break;
|
||||
case 'n':
|
||||
builder.Append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
builder.Append('\r');
|
||||
break;
|
||||
case 't':
|
||||
builder.Append('\t');
|
||||
break;
|
||||
case 'u':
|
||||
builder.Append((char)Convert.ToInt32(ReadChars(4), 16));
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(ch);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private object ParseNumber()
|
||||
{
|
||||
var token = NextToken();
|
||||
if (token.IndexOf('.') >= 0 || token.IndexOf('e') >= 0 || token.IndexOf('E') >= 0)
|
||||
{
|
||||
double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue);
|
||||
return doubleValue;
|
||||
}
|
||||
|
||||
long.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue);
|
||||
return longValue;
|
||||
}
|
||||
|
||||
private bool ParseTrue()
|
||||
{
|
||||
ReadChars(4);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ParseFalse()
|
||||
{
|
||||
ReadChars(5);
|
||||
return false;
|
||||
}
|
||||
|
||||
private object ParseNull()
|
||||
{
|
||||
ReadChars(4);
|
||||
return null;
|
||||
}
|
||||
|
||||
private string NextToken()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
while (_reader.Peek() != -1)
|
||||
{
|
||||
var ch = PeekChar;
|
||||
if (char.IsWhiteSpace(ch) || ch == ',' || ch == ']' || ch == '}')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
builder.Append(ReadChar());
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void EatWhitespace()
|
||||
{
|
||||
while (_reader.Peek() != -1 && char.IsWhiteSpace(PeekChar))
|
||||
{
|
||||
_reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
private string ReadChars(int count)
|
||||
{
|
||||
var buffer = new char[count];
|
||||
_reader.Read(buffer, 0, count);
|
||||
return new string(buffer);
|
||||
}
|
||||
|
||||
private char PeekChar => Convert.ToChar(_reader.Peek());
|
||||
|
||||
private char ReadChar() => Convert.ToChar(_reader.Read());
|
||||
}
|
||||
|
||||
private static class Serializer
|
||||
{
|
||||
public static string Serialize(object value)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
SerializeValue(value, builder);
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void SerializeValue(object value, StringBuilder builder)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
builder.Append("null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value is string str)
|
||||
{
|
||||
SerializeString(str, builder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value is bool boolean)
|
||||
{
|
||||
builder.Append(boolean ? "true" : "false");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value is IDictionary dictionary)
|
||||
{
|
||||
SerializeObject(dictionary, builder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value is IEnumerable enumerable && !(value is string))
|
||||
{
|
||||
SerializeArray(enumerable, builder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsNumeric(value))
|
||||
{
|
||||
builder.Append(Convert.ToString(value, CultureInfo.InvariantCulture));
|
||||
return;
|
||||
}
|
||||
|
||||
SerializeObject(ToReflectionDictionary(value), builder);
|
||||
}
|
||||
|
||||
private static void SerializeObject(IDictionary dictionary, StringBuilder builder)
|
||||
{
|
||||
var first = true;
|
||||
builder.Append('{');
|
||||
foreach (DictionaryEntry entry in dictionary)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
builder.Append(',');
|
||||
}
|
||||
|
||||
SerializeString(Convert.ToString(entry.Key, CultureInfo.InvariantCulture), builder);
|
||||
builder.Append(':');
|
||||
SerializeValue(entry.Value, builder);
|
||||
first = false;
|
||||
}
|
||||
|
||||
builder.Append('}');
|
||||
}
|
||||
|
||||
private static void SerializeArray(IEnumerable array, StringBuilder builder)
|
||||
{
|
||||
var first = true;
|
||||
builder.Append('[');
|
||||
foreach (var item in array)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
builder.Append(',');
|
||||
}
|
||||
|
||||
SerializeValue(item, builder);
|
||||
first = false;
|
||||
}
|
||||
|
||||
builder.Append(']');
|
||||
}
|
||||
|
||||
private static void SerializeString(string value, StringBuilder builder)
|
||||
{
|
||||
builder.Append('"');
|
||||
foreach (var ch in value)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '"':
|
||||
builder.Append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
builder.Append("\\\\");
|
||||
break;
|
||||
case '\b':
|
||||
builder.Append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
builder.Append("\\f");
|
||||
break;
|
||||
case '\n':
|
||||
builder.Append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
builder.Append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
builder.Append("\\t");
|
||||
break;
|
||||
default:
|
||||
if (ch < 32)
|
||||
{
|
||||
builder.Append("\\u");
|
||||
builder.Append(((int)ch).ToString("x4"));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append('"');
|
||||
}
|
||||
|
||||
private static bool IsNumeric(object value)
|
||||
{
|
||||
switch (Type.GetTypeCode(value.GetType()))
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.UInt32:
|
||||
case TypeCode.UInt64:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.Int64:
|
||||
case TypeCode.Decimal:
|
||||
case TypeCode.Double:
|
||||
case TypeCode.Single:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static IDictionary ToReflectionDictionary(object value)
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
var type = value.GetType();
|
||||
|
||||
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
result[field.Name] = field.GetValue(value);
|
||||
}
|
||||
|
||||
foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
if (!property.CanRead || property.GetIndexParameters().Length > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result[property.Name] = property.GetValue(value, null);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c594424856e2e74abbce01c4ad4532d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,309 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
internal static class BriskModelMapper
|
||||
{
|
||||
public static BriskLoginResult ToLoginResult(Dictionary<string, object> data)
|
||||
{
|
||||
var profileData = BriskValueReader.GetDictionary(data, "profile");
|
||||
return new BriskLoginResult
|
||||
{
|
||||
AccessToken = BriskValueReader.GetString(data, "access_token"),
|
||||
ExpiresIn = BriskValueReader.GetInt(data, "expires_in"),
|
||||
PlayerId = BriskValueReader.GetString(data, "player_id"),
|
||||
ProjectAccountId = BriskValueReader.GetString(data, "project_account_id"),
|
||||
LoginProvider = BriskValueReader.GetString(profileData, "login_provider"),
|
||||
LoginUserId = BriskValueReader.GetString(profileData, "login_user_id"),
|
||||
IsNewPlayer = BriskValueReader.GetBool(data, "is_new_player"),
|
||||
Profile = ToProfile(profileData)
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskBootstrapResult ToBootstrapResult(Dictionary<string, object> data)
|
||||
{
|
||||
return new BriskBootstrapResult
|
||||
{
|
||||
ProjectName = BriskValueReader.GetString(data, "project_name"),
|
||||
ServerTime = BriskValueReader.GetString(data, "server_time"),
|
||||
MaintenanceMode = BriskValueReader.GetBool(data, "maintenance_mode"),
|
||||
MaintenanceMessage = BriskValueReader.GetString(data, "maintenance_message"),
|
||||
MinClientVersion = BriskValueReader.GetString(data, "min_client_version"),
|
||||
FeatureFlags = BriskValueReader.GetDictionary(data, "feature_flags") ?? new Dictionary<string, object>(),
|
||||
DynamicConfig = BriskValueReader.GetDictionary(data, "dynamic_config") ?? new Dictionary<string, object>()
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskConfigCurrent ToConfigCurrent(Dictionary<string, object> data)
|
||||
{
|
||||
return new BriskConfigCurrent
|
||||
{
|
||||
FeatureFlags = BriskValueReader.GetDictionary(data, "feature_flags") ?? new Dictionary<string, object>(),
|
||||
DynamicConfig = BriskValueReader.GetDictionary(data, "dynamic_config") ?? new Dictionary<string, object>()
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskPlayerMe ToPlayerMe(Dictionary<string, object> data)
|
||||
{
|
||||
return new BriskPlayerMe
|
||||
{
|
||||
PlayerId = BriskValueReader.GetString(data, "player_id"),
|
||||
ProjectAccountId = BriskValueReader.GetString(data, "project_account_id"),
|
||||
LoginProvider = BriskValueReader.GetString(data, "login_provider"),
|
||||
LoginUserId = BriskValueReader.GetString(data, "login_user_id"),
|
||||
Nickname = BriskValueReader.GetString(data, "nickname"),
|
||||
AvatarUrl = BriskValueReader.GetString(data, "avatar_url"),
|
||||
ProfileJson = BriskValueReader.GetDictionary(data, "profile_json")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskProfile ToProfile(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskProfile
|
||||
{
|
||||
Nickname = BriskValueReader.GetString(data, "nickname"),
|
||||
AvatarUrl = BriskValueReader.GetString(data, "avatar_url"),
|
||||
ProfileJson = BriskValueReader.GetDictionary(data, "profile_json")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskLeaderboardEntry ToLeaderboardEntry(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskLeaderboardEntry
|
||||
{
|
||||
Rank = BriskValueReader.GetInt(data, "rank"),
|
||||
PlayerId = BriskValueReader.GetString(data, "player_id"),
|
||||
ProjectAccountId = BriskValueReader.GetString(data, "project_account_id"),
|
||||
LoginProvider = BriskValueReader.GetString(data, "login_provider"),
|
||||
LoginUserId = BriskValueReader.GetString(data, "login_user_id"),
|
||||
Score = BriskValueReader.GetLong(data, "score"),
|
||||
Nickname = BriskValueReader.GetString(data, "nickname"),
|
||||
AvatarUrl = BriskValueReader.GetString(data, "avatar_url")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskLeaderboardPlayerRank ToLeaderboardPlayerRank(Dictionary<string, object> data)
|
||||
{
|
||||
var entry = ToLeaderboardEntry(data);
|
||||
if (entry == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskLeaderboardPlayerRank
|
||||
{
|
||||
Rank = entry.Rank,
|
||||
Score = entry.Score,
|
||||
PlayerId = entry.PlayerId,
|
||||
ProjectAccountId = entry.ProjectAccountId,
|
||||
LoginProvider = entry.LoginProvider,
|
||||
LoginUserId = entry.LoginUserId,
|
||||
Nickname = entry.Nickname,
|
||||
AvatarUrl = entry.AvatarUrl
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskRankSeasonInfo ToRankSeasonInfo(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskRankSeasonInfo
|
||||
{
|
||||
SeasonId = BriskValueReader.GetString(data, "season_id") ?? BriskValueReader.GetString(data, "id"),
|
||||
Name = BriskValueReader.GetString(data, "name"),
|
||||
StartAt = BriskValueReader.GetString(data, "start_at"),
|
||||
EndAt = BriskValueReader.GetString(data, "end_at")
|
||||
};
|
||||
}
|
||||
|
||||
public static List<BriskLeaderboardEntry> ToLeaderboardEntries(object data)
|
||||
{
|
||||
return ExtractList(data)
|
||||
.Select(item => ToLeaderboardEntry(item as Dictionary<string, object>))
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static List<BriskRankSeasonInfo> ToRankSeasonInfos(object data)
|
||||
{
|
||||
return ExtractList(data)
|
||||
.Select(item => ToRankSeasonInfo(item as Dictionary<string, object>))
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static BriskAnnouncementItem ToAnnouncementItem(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskAnnouncementItem
|
||||
{
|
||||
Id = BriskValueReader.GetLong(data, "id"),
|
||||
Title = BriskValueReader.GetString(data, "title"),
|
||||
Content = BriskValueReader.GetString(data, "content"),
|
||||
ContentType = BriskValueReader.GetString(data, "content_type"),
|
||||
StartAt = BriskValueReader.GetString(data, "start_at"),
|
||||
EndAt = BriskValueReader.GetString(data, "end_at"),
|
||||
IsRead = BriskValueReader.GetBool(data, "is_read")
|
||||
};
|
||||
}
|
||||
|
||||
public static List<BriskAnnouncementItem> ToAnnouncementItems(object data)
|
||||
{
|
||||
return ExtractList(data)
|
||||
.Select(item => ToAnnouncementItem(item as Dictionary<string, object>))
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static BriskArchiveSlot ToArchiveSlot(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskArchiveSlot
|
||||
{
|
||||
SlotNo = BriskValueReader.GetInt(data, "slot_no"),
|
||||
Version = BriskValueReader.GetInt(data, "version"),
|
||||
SizeBytes = BriskValueReader.GetLong(data, "size_bytes"),
|
||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskArchiveMeta ToArchiveMeta(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskArchiveMeta
|
||||
{
|
||||
SlotNo = BriskValueReader.GetInt(data, "slot_no"),
|
||||
Version = BriskValueReader.GetInt(data, "version"),
|
||||
Checksum = BriskValueReader.GetString(data, "checksum"),
|
||||
SizeBytes = BriskValueReader.GetLong(data, "size_bytes"),
|
||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskArchiveUploadResult ToArchiveUploadResult(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskArchiveUploadResult
|
||||
{
|
||||
SlotNo = BriskValueReader.GetInt(data, "slot_no"),
|
||||
Version = BriskValueReader.GetInt(data, "version"),
|
||||
SizeBytes = BriskValueReader.GetLong(data, "size_bytes"),
|
||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||
};
|
||||
}
|
||||
|
||||
public static List<BriskArchiveSlot> ToArchiveSlots(object data)
|
||||
{
|
||||
return ExtractList(data)
|
||||
.Select(item => ToArchiveSlot(item as Dictionary<string, object>))
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static BriskSpaceView ToSpaceView(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskSpaceView
|
||||
{
|
||||
PlayerId = BriskValueReader.GetString(data, "player_id"),
|
||||
LoginProvider = BriskValueReader.GetString(data, "login_provider"),
|
||||
LoginUserId = BriskValueReader.GetString(data, "login_user_id"),
|
||||
Payload = BriskValueReader.GetDictionary(data, "payload_json") ?? BriskValueReader.GetDictionary(data, "payload")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskSpaceStats ToSpaceStats(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskSpaceStats
|
||||
{
|
||||
LikeCount = BriskValueReader.GetInt(data, "like_count"),
|
||||
VisitCount = BriskValueReader.GetInt(data, "visit_count")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskSpaceVisit ToSpaceVisit(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskSpaceVisit
|
||||
{
|
||||
VisitorPlayerId = BriskValueReader.GetString(data, "visitor_player_id") ?? BriskValueReader.GetString(data, "player_id"),
|
||||
VisitorLoginProvider = BriskValueReader.GetString(data, "visitor_login_provider") ?? BriskValueReader.GetString(data, "login_provider"),
|
||||
VisitorLoginUserId = BriskValueReader.GetString(data, "visitor_login_user_id") ?? BriskValueReader.GetString(data, "login_user_id"),
|
||||
VisitorNickname = BriskValueReader.GetString(data, "visitor_nickname") ?? BriskValueReader.GetString(data, "nickname"),
|
||||
VisitorAvatarUrl = BriskValueReader.GetString(data, "visitor_avatar_url") ?? BriskValueReader.GetString(data, "avatar_url"),
|
||||
VisitedAt = BriskValueReader.GetString(data, "visited_at")
|
||||
};
|
||||
}
|
||||
|
||||
public static List<BriskSpaceVisit> ToSpaceVisits(object data)
|
||||
{
|
||||
return ExtractList(data)
|
||||
.Select(item => ToSpaceVisit(item as Dictionary<string, object>))
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> ExtractObject(object data)
|
||||
{
|
||||
return data as Dictionary<string, object>;
|
||||
}
|
||||
|
||||
private static List<object> ExtractList(object data)
|
||||
{
|
||||
if (data is List<object> list)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
if (data is Dictionary<string, object> dictionary)
|
||||
{
|
||||
return BriskValueReader.GetList(dictionary, "items")
|
||||
?? BriskValueReader.GetList(dictionary, "list")
|
||||
?? BriskValueReader.GetList(dictionary, "records")
|
||||
?? new List<object>();
|
||||
}
|
||||
|
||||
return new List<object>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22a86853f3fd851428010367935b7275
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public abstract class BriskModuleBase
|
||||
{
|
||||
protected static BriskContext GetContext()
|
||||
{
|
||||
return Brisk.GetRequiredContext();
|
||||
}
|
||||
|
||||
protected static Task<T> ExecuteAsync<T>(Func<BriskContext, Task<T>> action)
|
||||
{
|
||||
return BriskModuleExecutor.ExecuteAsync(action);
|
||||
}
|
||||
|
||||
protected static Task ExecuteAsync(Func<BriskContext, Task> action)
|
||||
{
|
||||
return BriskModuleExecutor.ExecuteAsync(action);
|
||||
}
|
||||
|
||||
protected static Task<T> ExecutePublicAsync<T>(Func<BriskContext, Task<T>> action)
|
||||
{
|
||||
return BriskModuleExecutor.ExecutePublicAsync(action);
|
||||
}
|
||||
|
||||
protected static Task ExecutePublicAsync(Func<BriskContext, Task> action)
|
||||
{
|
||||
return BriskModuleExecutor.ExecutePublicAsync(action);
|
||||
}
|
||||
|
||||
protected static string RequireNotEmpty(string value, string paramName)
|
||||
{
|
||||
GetContext();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new ArgumentException(paramName + " is required.", paramName);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected static int RequirePositive(int value, string paramName, string message)
|
||||
{
|
||||
GetContext();
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(paramName, message);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected static long RequirePositive(long value, string paramName, string message)
|
||||
{
|
||||
GetContext();
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(paramName, message);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected static T RequireNotNull<T>(T value, string paramName) where T : class
|
||||
{
|
||||
GetContext();
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b622a31e3d5440a0b1d442ca5ce85b02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal static class BriskModuleExecutor
|
||||
{
|
||||
public static Task<T> ExecuteAsync<T>(Func<BriskContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteAsync(action, clearSessionOnAuthExpired: true);
|
||||
}
|
||||
|
||||
public static Task<T> ExecutePublicAsync<T>(Func<BriskContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteAsync(action, clearSessionOnAuthExpired: false);
|
||||
}
|
||||
|
||||
public static async Task ExecuteAsync(Func<BriskContext, Task> action)
|
||||
{
|
||||
await ExecuteAsync(async context =>
|
||||
{
|
||||
await action(context);
|
||||
return true;
|
||||
}, clearSessionOnAuthExpired: true);
|
||||
}
|
||||
|
||||
public static async Task ExecutePublicAsync(Func<BriskContext, Task> action)
|
||||
{
|
||||
await ExecuteAsync(async context =>
|
||||
{
|
||||
await action(context);
|
||||
return true;
|
||||
}, clearSessionOnAuthExpired: false);
|
||||
}
|
||||
|
||||
private static async Task<T> ExecuteAsync<T>(Func<BriskContext, Task<T>> action, bool clearSessionOnAuthExpired)
|
||||
{
|
||||
var context = Brisk.GetRequiredContext();
|
||||
|
||||
try
|
||||
{
|
||||
return await action(context);
|
||||
}
|
||||
catch (BriskAuthExpiredException exception)
|
||||
{
|
||||
if (clearSessionOnAuthExpired)
|
||||
{
|
||||
context.Session.Clear();
|
||||
await context.TokenStore.ClearAsync();
|
||||
Brisk.NotifyAuthExpired(exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (BriskBlockingException exception)
|
||||
{
|
||||
Brisk.NotifyBlockingError(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28dcdf91da7cd78469e32dfb10a0c909
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
public sealed class BriskOptions
|
||||
{
|
||||
public string BaseUrl;
|
||||
public string GameKey;
|
||||
public string ClientVersion;
|
||||
public string DeviceId;
|
||||
public bool EnableLog;
|
||||
public bool ValidateSessionOnInitialize = true;
|
||||
public IBriskTokenStore TokenStore;
|
||||
public IBriskErrorPresenter ErrorPresenter;
|
||||
public Action ExitHandler;
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(BaseUrl))
|
||||
{
|
||||
throw new ArgumentException("BriskOptions.BaseUrl is required.", nameof(BaseUrl));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(GameKey))
|
||||
{
|
||||
throw new ArgumentException("BriskOptions.GameKey is required.", nameof(GameKey));
|
||||
}
|
||||
|
||||
BaseUrl = NormalizeBaseUrl(BaseUrl);
|
||||
}
|
||||
|
||||
private static string NormalizeBaseUrl(string baseUrl)
|
||||
{
|
||||
var normalized = baseUrl.Trim().TrimEnd('/');
|
||||
if (normalized.EndsWith("/api", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return normalized + "/api";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26bc6b9d30c442e44bffdc02dde79df8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
public sealed class BriskSession
|
||||
{
|
||||
public string AccessToken { get; private set; }
|
||||
|
||||
public DateTimeOffset? ExpiresAt { get; private set; }
|
||||
|
||||
public BriskIdentity Identity { get; private set; } = new BriskIdentity();
|
||||
|
||||
public string PlayerId => Identity.PlayerId;
|
||||
|
||||
public string ProjectAccountId => Identity.ProjectAccountId;
|
||||
|
||||
public string LoginProvider => Identity.LoginProvider;
|
||||
|
||||
public string LoginUserId => Identity.LoginUserId;
|
||||
|
||||
public bool HasAccessToken => !string.IsNullOrWhiteSpace(AccessToken);
|
||||
|
||||
public void Update(string accessToken, DateTimeOffset? expiresAt, string playerId, string projectAccountId, string loginProvider = null, string loginUserId = null)
|
||||
{
|
||||
AccessToken = accessToken;
|
||||
ExpiresAt = expiresAt;
|
||||
Identity = new BriskIdentity
|
||||
{
|
||||
PlayerId = playerId,
|
||||
ProjectAccountId = projectAccountId,
|
||||
LoginProvider = loginProvider,
|
||||
LoginUserId = loginUserId
|
||||
};
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
AccessToken = null;
|
||||
ExpiresAt = null;
|
||||
Identity = new BriskIdentity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0066dd2f84510c74b90f5886bdafd97f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
internal static class BriskValueReader
|
||||
{
|
||||
public static string GetString(Dictionary<string, object> data, string key)
|
||||
{
|
||||
if (!TryGetValue(data, key, out var value) || value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Convert.ToString(value);
|
||||
}
|
||||
|
||||
public static bool GetBool(Dictionary<string, object> data, string key)
|
||||
{
|
||||
if (!TryGetValue(data, key, out var value) || value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return boolValue;
|
||||
}
|
||||
|
||||
if (bool.TryParse(Convert.ToString(value), out var parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int GetInt(Dictionary<string, object> data, string key)
|
||||
{
|
||||
if (!TryGetValue(data, key, out var value) || value == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static long GetLong(Dictionary<string, object> data, string key)
|
||||
{
|
||||
if (!TryGetValue(data, key, out var value) || value == null)
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Convert.ToInt64(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GetDictionary(Dictionary<string, object> data, string key)
|
||||
{
|
||||
if (!TryGetValue(data, key, out var value) || value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value as Dictionary<string, object>;
|
||||
}
|
||||
|
||||
public static List<object> GetList(Dictionary<string, object> data, string key)
|
||||
{
|
||||
if (!TryGetValue(data, key, out var value) || value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value as List<object>;
|
||||
}
|
||||
|
||||
public static bool TryGetValue(Dictionary<string, object> data, string key, out object value)
|
||||
{
|
||||
value = null;
|
||||
return data != null && !string.IsNullOrWhiteSpace(key) && data.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21fc7c890cc76b441a502dc8b2f4d232
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
internal static class BriskVersionComparer
|
||||
{
|
||||
public static bool IsLessThan(string currentVersion, string minVersion)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(currentVersion) || string.IsNullOrWhiteSpace(minVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Version.TryParse(Normalize(currentVersion), out var current))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Version.TryParse(Normalize(minVersion), out var minimum))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return current < minimum;
|
||||
}
|
||||
|
||||
private static string Normalize(string version)
|
||||
{
|
||||
var normalized = version.Trim();
|
||||
return normalized.StartsWith("v", StringComparison.OrdinalIgnoreCase)
|
||||
? normalized.Substring(1)
|
||||
: normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b6a0a279968c9d4b8cd139adfc5cfc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
public interface IBriskErrorPresenter
|
||||
{
|
||||
void ShowBlockingError(BriskBlockingException exception);
|
||||
|
||||
void ShowAuthExpired(BriskAuthExpiredException exception);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f86045e1bf5812748b28e3009dea58b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IBriskTokenStore
|
||||
{
|
||||
Task SaveAsync(BriskStoredSession session);
|
||||
|
||||
Task<BriskStoredSession> LoadAsync();
|
||||
|
||||
Task ClearAsync();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c47e47902831504b9a76794f94bc9a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user