2026-04-10 22:04:51 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Brisk SDK 的静态总入口。
|
|
|
|
|
|
/// 初始化完成后,开发者可以通过 <c>Brisk.Auth</c>、<c>Brisk.Leaderboard</c> 等模块直接访问能力。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// SDK 初始化完成后触发。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static event Action OnInitialized;
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 登录成功并保存会话后触发。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static event Action OnLoggedIn;
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 主动登出并清理本地会话后触发。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static event Action OnLoggedOut;
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 发生维护、封禁、强更等严重阻断错误时触发。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static event Action<BriskBlockingException> OnBlockingError;
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 登录态失效并清空本地会话时触发。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static event Action<BriskAuthExpiredException> OnAuthExpired;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 认证模块入口。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskAuthModule Auth { get; }
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 玩家模块入口。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskPlayerModule Player { get; }
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 动态配置模块入口。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskConfigModule Config { get; }
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 公告模块入口。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskAnnouncementsModule Announcements { get; }
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 排行榜模块入口。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskLeaderboardModule Leaderboard { get; }
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 云存档模块入口。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskArchiveModule Archive { get; }
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 玩家空间模块入口。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskSpaceModule Space { get; }
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前 SDK 是否已完成初始化。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static bool IsInitialized => s_context != null;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前是否存在可用登录态。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static bool IsLoggedIn => s_context != null && s_context.Session.HasAccessToken;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前访问令牌。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static string AccessToken => s_context != null ? s_context.Session.AccessToken : null;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前玩家 ID。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static string PlayerId => s_context != null ? s_context.Session.PlayerId : null;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前登录身份摘要。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskIdentity Identity => s_context != null ? s_context.Session.Identity : null;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前初始化选项。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskOptions Options => s_context != null ? s_context.Options : null;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化阶段获取到的 bootstrap 结果。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static BriskBootstrapResult Bootstrap => s_context != null ? s_context.Bootstrap : null;
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化 SDK,并执行 bootstrap 与本地会话恢复。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="options">初始化选项。</param>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 关闭 SDK 并清空当前上下文。
|
|
|
|
|
|
/// </summary>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static void Shutdown()
|
|
|
|
|
|
{
|
|
|
|
|
|
s_context = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置严重错误的展示器。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="presenter">自定义错误展示器;传入 null 时恢复默认展示器。</param>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
public static void SetErrorPresenter(IBriskErrorPresenter presenter)
|
|
|
|
|
|
{
|
|
|
|
|
|
GetRequiredContext().ErrorPresenter = presenter ?? BriskDefaultErrorPresenter.Instance;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置阻断错误确认后的退出回调。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="exitHandler">项目方自定义退出逻辑。</param>
|
2026-04-10 22:04:51 +08:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-05-13 19:05:00 +08:00
|
|
|
|
NotifyAuthExpired(exception, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal static void NotifyAuthExpired(BriskAuthExpiredException exception, bool showDefaultPresenter)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (showDefaultPresenter)
|
|
|
|
|
|
{
|
|
|
|
|
|
s_context?.ErrorPresenter?.ShowAuthExpired(exception);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 22:04:51 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 19:05:00 +08:00
|
|
|
|
if (storedSession.ExpiresAt.HasValue && storedSession.ExpiresAt.Value <= DateTimeOffset.UtcNow)
|
|
|
|
|
|
{
|
|
|
|
|
|
await HandleExpiredStartupSessionAsync(
|
|
|
|
|
|
context,
|
|
|
|
|
|
storedSession,
|
|
|
|
|
|
new BriskAuthExpiredException("Stored session expired before initialization."));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 22:04:51 +08:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-05-13 19:05:00 +08:00
|
|
|
|
await HandleExpiredStartupSessionAsync(context, storedSession, exception);
|
2026-04-10 22:04:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (BriskBlockingException exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
NotifyBlockingError(exception);
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-13 19:05:00 +08:00
|
|
|
|
|
|
|
|
|
|
private static async Task HandleExpiredStartupSessionAsync(BriskContext context, BriskStoredSession storedSession, BriskAuthExpiredException exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
context.Session.Clear();
|
|
|
|
|
|
await context.TokenStore.ClearAsync();
|
|
|
|
|
|
NotifyAuthExpired(exception, false);
|
|
|
|
|
|
|
|
|
|
|
|
if (!context.Options.AutoReloginOnInitialize || !HasStoredLoginIdentity(storedSession))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Auth.LoginWithUserIdAsync(storedSession.LoginProvider, storedSession.LoginUserId);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (BriskBlockingException)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 初始化期静默重登失败时,交给业务层后续第三方登录流程重新调用 Brisk.Auth。
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static bool HasStoredLoginIdentity(BriskStoredSession storedSession)
|
|
|
|
|
|
{
|
|
|
|
|
|
return storedSession != null
|
|
|
|
|
|
&& !string.IsNullOrWhiteSpace(storedSession.LoginProvider)
|
|
|
|
|
|
&& !string.IsNullOrWhiteSpace(storedSession.LoginUserId);
|
|
|
|
|
|
}
|
2026-04-10 22:04:51 +08:00
|
|
|
|
}
|