QuestShare/QuestShare.Dalamud/Services/SocketClientService.cs
2025-02-17 22:12:35 -05:00

470 lines
19 KiB
C#

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using QuestShare.Common.API;
using QuestShare.Common.API.Share;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace QuestShare.Services
{
internal class SocketClientService
{
private readonly string socketUrl = "http://localhost:8080/Hub";
internal static HubConnection connection { get; private set; } = null!;
internal static bool IsConnected => connection.State == HubConnectionState.Connected;
internal static bool IsAuthorized { get; private set; } = false;
private static bool IsDisposing = false;
public event EventHandler<SocketEventArgs> OnCancelEvent = delegate { };
public event EventHandler<SocketEventArgs> OnGroupEvent = delegate { };
public event EventHandler<SocketEventArgs> OnUngroupEvent = delegate { };
public event EventHandler<SocketEventArgs> OnGetEvent = delegate { };
public event EventHandler<SocketEventArgs> OnRegisterEvent = delegate { };
public event EventHandler<SocketEventArgs> OnUpdateEvent = delegate { };
public event EventHandler<TokenCheckEventArgs> OnTokenCheckEvent = delegate { };
public SocketClientService()
{
var builder = new HubConnectionBuilder().WithUrl(socketUrl).ConfigureLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Information).AddConsole();
});
connection = builder.Build();
connection.Closed += async (error) =>
{
if (IsDisposing) return;
Log.Warning($"Connection closed... {error}");
Log.Warning($"Connection closed, retrying... {error}");
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
connection.Reconnected += async (error) =>
{
Log.Information("Connection reconnected");
};
connection.On<Register.Response>(nameof(Register), Client_Register);
connection.On<Update.Response>(nameof(Update), Client_Update);
connection.On<Update.UpdateBroadcast>(nameof(Update.UpdateBroadcast), Client_UpdateBroadcast);
connection.On<GetShareInfo.Response>(nameof(GetShareInfo), Client_GetShareInfo);
connection.On<Cancel.Response>(nameof(Cancel), Client_Cancel);
connection.On<Cancel.CancelBroadcast>(nameof(Cancel.CancelBroadcast), Client_CancelBroadast);
connection.On<GroupJoin.Response>(nameof(GroupJoin), Client_GroupJoin);
connection.On<GroupLeave.Response>(nameof(GroupLeave), Client_GroupLeave);
connection.On<Authorize.Response>(nameof(Authorize), Client_Authorize);
connection.On<GroupNotify.GroupNotifyBroadcast>(nameof(GroupNotify.GroupNotifyBroadcast), Client_GroupNotify);
connection.On<Resume.Response>(nameof(Resume), Client_Resume);
connection.On<Authorize.AuthBroadcast>(nameof(Authorize.AuthBroadcast), Client_AuthRequest);
ClientState.Login += OnLogin;
ClientState.Logout += OnLogout;
}
public void Dispose()
{
IsDisposing = true;
if (IsConnected)
{
connection.StopAsync();
}
connection.DisposeAsync();
ClientState.Login -= OnLogin;
ClientState.Logout -= OnLogout;
}
public void OnLogin()
{
if (Plugin.Configuration.Instance.Token != "")
{
ShareService.Token = Plugin.Configuration.Instance.Token;
DispatchAuthorize();
}
}
public void OnLogout(int code, int type)
{
ShareService.Token = "";
Plugin.Configuration.Save();
IsAuthorized = false;
connection.StopAsync().ConfigureAwait(false);
}
public async Task Connect()
{
try
{
if (IsConnected) await connection.StopAsync();
IsDisposing = false;
await connection.StartAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
Log.Error("Failed to connect to socket server");
}
else
{
Log.Information("Connected to socket server");
}
});
}
catch (Exception ex)
{
Log.Error(ex.Message);
}
}
public async Task Disconnect()
{
IsDisposing = true;
await connection.StopAsync();
}
private async Task Invoke(string methodName, object request)
{
if (!IsConnected) await Connect();
Log.Debug($"Invoking {methodName} with {JsonConvert.SerializeObject(request)}");
var s = new StackTrace();
Log.Debug(s.ToString());
await connection.InvokeAsync(methodName, request);
}
public static void DispatchAuthorize()
{
Plugin.SocketClient.Invoke(nameof(Authorize), new Authorize.Request
{
Version = Constants.Version,
Token = ShareService.Token,
CharacterId = ClientState.LocalContentId
}).ConfigureAwait(false);
}
public static void DispatchRegister()
{
if (!IsAuthorized)
{
Log.Warning("Not authorized to register");
return;
}
var activeQuest = GameQuestManager.GetActiveQuest();
Plugin.SocketClient.Invoke(nameof(Register), new Register.Request
{
Version = Constants.Version,
Token = ShareService.Token,
CharacterId = ClientState.LocalContentId,
SharedQuestId = activeQuest != null ? activeQuest.QuestId : 0,
SharedQuestStep = activeQuest != null ? activeQuest.CurrentStep : (byte)0,
}).ConfigureAwait(false);
}
public static void DispatchGroup(string shareCode)
{
Plugin.SocketClient.Invoke(nameof(GroupJoin), new GroupJoin.Request
{
Token = ShareService.Token,
ShareCode = shareCode,
Version = Constants.Version,
CharacterId = ClientState.LocalContentId
}).ConfigureAwait(false);
}
public static void DispatchUngroup()
{
Plugin.SocketClient.Invoke(nameof(GroupLeave), new GroupLeave.Request
{
Token = ShareService.Token,
ShareCode = ShareService.ShareCode,
Version = Constants.Version,
}).ConfigureAwait(false);
}
public static void DispatchResume()
{
var members = new List<ulong>();
foreach (var member in PartyList)
{
members.Add((ulong)member.ContentId);
}
Plugin.SocketClient.Invoke(nameof(Resume), new Resume.Request
{
Token = ShareService.Token,
Version = Constants.Version,
Members = members,
ShareCode = Plugin.Configuration.Instance.LastShareCode,
}).ConfigureAwait(false);
}
public static void DispatchGetShareInfo()
{
Plugin.SocketClient.Invoke(nameof(GetShareInfo), new GetShareInfo.Request
{
Token = ShareService.Token,
Version = Constants.Version,
ShareCode = ShareService.ShareCode
}).ConfigureAwait(false);
}
public static void DispatchCancel()
{
Plugin.SocketClient.Invoke(nameof(Cancel), new Cancel.Request
{
Token = ShareService.Token,
Version = Constants.Version,
ShareCode = ShareService.HostedShareCode
}).ConfigureAwait(false);
}
public static void DispatchUpdate(bool partyUpdate)
{
if (!ShareService.IsRegistered || !ShareService.IsGrouped)
{
return;
}
var activeQuest = GameQuestManager.GetActiveQuest();
Plugin.SocketClient.Invoke(nameof(Update), new Update.Request
{
Token = ShareService.Token,
Version = Constants.Version,
SharedQuestId = activeQuest != null ? activeQuest.QuestId : 0,
SharedQuestStep = activeQuest != null ? activeQuest.CurrentStep : (byte)0,
BroadcastParty = Plugin.Configuration.Instance.BroadcastToParty,
PartyMembers = ShareService.IsHost ? ShareService.PartyMembers : [],
IsPartyChange = partyUpdate
}).ConfigureAwait(false);
}
#region Server Methods
private Task Client_Register(Register.Response response)
{
Log.Debug($"Client_Register({JsonConvert.SerializeObject(response)})");
if (response.Success)
{
ShareService.SetHostedShareCode(response.ShareCode);
ShareService.IsHost = true;
ShareService.IsGrouped = false;
OnRegisterEvent.Invoke(this, new SocketEventArgs { Success = true });
}
else
{
OnRegisterEvent.Invoke(this, new SocketEventArgs { Success = false, Message = response.Error.ToString() });
}
return Task.CompletedTask;
}
private Task Client_Update(Update.Response response)
{
Log.Debug($"Client_Update({JsonConvert.SerializeObject(response)})");
if (response.Success)
{
OnUpdateEvent.Invoke(this, new SocketEventArgs { Success = true });
}
else
{
OnUpdateEvent.Invoke(this, new SocketEventArgs { Success = false, Message = response.Error.ToString() });
}
return Task.CompletedTask;
}
private Task Client_UpdateBroadcast(Update.UpdateBroadcast broadcast)
{
Log.Debug($"Client_UpdateBroadcast({JsonConvert.SerializeObject(broadcast)})");
ShareService.SetActiveQuest(broadcast.SharedQuestId, broadcast.SharedQuestStep);
Log.Debug($"Updated quest: {broadcast.SharedQuestId} - {broadcast.SharedQuestStep}");
return Task.CompletedTask;
}
private Task Client_GroupJoin(GroupJoin.Response response)
{
Log.Debug($"Client_GroupJoin({JsonConvert.SerializeObject(response)})");
if (response.Success)
{
Plugin.Configuration.Instance.LastShareCode = response.ShareCode;
Plugin.Configuration.Save();
ShareService.IsHost = false;
ShareService.IsGrouped = true;
ShareService.SetActiveQuest(response.SharedQuestId, response.SharedQuestStep);
ShareService.SetShareCode(response.ShareCode);
OnGroupEvent.Invoke(this, new SocketEventArgs { Success = true });
Log.Debug($"Joined group: {response.ShareCode}");
}
else
{
Log.Error($"Failed to join group: {response.Error}");
OnGroupEvent.Invoke(this, new SocketEventArgs { Success = false, Message = response.Error.ToString() });
}
return Task.CompletedTask;
}
private Task Client_GroupLeave(GroupLeave.Response response)
{
Log.Debug($"Client_GroupLeave({JsonConvert.SerializeObject(response)})");
if (response.Success)
{
ShareService.IsGrouped = false;
ShareService.IsHost = false;
ShareService.ActiveQuestId = 0;
ShareService.ActiveQuestStep = 0;
OnUngroupEvent.Invoke(this, new SocketEventArgs { Success = true });
Log.Debug("Left group");
}
else
{
Log.Error($"Failed to leave group: {response.Error}");
OnUngroupEvent.Invoke(this, new SocketEventArgs { Success = false, Message = response.Error.ToString() });
}
return Task.CompletedTask;
}
private Task Client_Resume(Resume.Response response)
{
Log.Debug($"Client_Resume({JsonConvert.SerializeObject(response)})");
if (response.Success)
{
if (response.IsHost)
{
ShareService.PartyMembers = response.Members ?? [];
ShareService.IsHost = response.IsHost;
ShareService.IsGrouped = false;
ShareService.SetHostedShareCode(response.ShareCode);
GameQuestManager.SetActiveFlag(response.SharedQuestId);
} else
{
ShareService.PartyMembers = response.Members ?? [];
ShareService.IsHost = false;
ShareService.IsGrouped = true;
ShareService.SetActiveQuest(response.SharedQuestId, response.SharedQuestStep);
}
OnGetEvent.Invoke(this, new SocketEventArgs { Success = true });
Log.Debug($"Resumed share: {response.ShareCode}");
DispatchUpdate(false);
}
else
{
Log.Warning($"Failed to resume share: {response.Error}");
ShareService.ActiveQuestId = 0;
ShareService.ActiveQuestStep = 0;
ShareService.SetShareCode("");
ShareService.IsGrouped = false;
ShareService.IsHost = false;
ShareService.PartyMembers = [];
OnGetEvent.Invoke(this, new SocketEventArgs { Success = false, Message = response.Error.ToString() });
}
return Task.CompletedTask;
}
private Task Client_Authorize(Authorize.Response response)
{
Log.Debug($"Client_Authorize({JsonConvert.SerializeObject(response)})");
if (response.Success)
{
ShareService.Token = response.Token;
Plugin.Configuration.Instance.Token = response.Token;
Plugin.Configuration.Save();
Log.Debug("Logged in");
IsAuthorized = true;
}
else
{
Log.Error($"Failed to authorize: {response.Error}");
}
DispatchResume();
return Task.CompletedTask;
}
private Task Client_AuthRequest(Authorize.AuthBroadcast _)
{
Log.Debug("Received auth request");
DispatchAuthorize();
return Task.CompletedTask;
}
private Task Client_Cancel(Cancel.Response response)
{
Log.Debug($"Client_Cancel({JsonConvert.SerializeObject(response)})");
if (response.Success)
{
ShareService.SetActiveQuest(0, 0);
ShareService.SetShareCode("");
ShareService.IsGrouped = false;
ShareService.IsHost = false;
OnCancelEvent.Invoke(this, new SocketEventArgs { Success = true });
Log.Debug("Cancelled share");
}
else
{
Log.Error($"Failed to cancel share: {response.Error}");
ShareService.SetActiveQuest(0, 0);
ShareService.SetShareCode("");
ShareService.IsGrouped = false;
ShareService.IsHost = false;
OnCancelEvent.Invoke(this, new SocketEventArgs { Success = false, Message = response.Error.ToString() });
}
return Task.CompletedTask;
}
private Task Client_CancelBroadast(Cancel.CancelBroadcast broadcast)
{
Log.Debug($"Client_CancelBroadcast({JsonConvert.SerializeObject(broadcast)})");
ShareService.SetActiveQuest(0, 0);
ShareService.SetShareCode("");
ShareService.IsGrouped = false;
ShareService.IsHost = false;
OnCancelEvent.Invoke(this, new SocketEventArgs { Success = true });
Log.Debug("Cancelled share");
return Task.CompletedTask;
}
private Task Client_GetShareInfo(GetShareInfo.Response response)
{
Log.Debug($"Client_GetShareInfo({JsonConvert.SerializeObject(response)})");
if (response.Success)
{
ShareService.SetActiveQuest(response.SharedQuestId, response.SharedQuestStep);
ShareService.PartyMembers = response.Members ?? [];
OnGetEvent.Invoke(this, new SocketEventArgs { Success = true });
Log.Debug($"Received share info: {response}");
}
else
{
Log.Error($"Failed to get share info: {response.Error}");
OnGetEvent.Invoke(this, new SocketEventArgs { Success = false, Message = response.Error.ToString() });
}
return Task.CompletedTask;
}
private Task Client_GroupNotify(GroupNotify.GroupNotifyBroadcast broadcast)
{
Log.Debug($"Client_GroupNotify({JsonConvert.SerializeObject(broadcast)})");
if (broadcast.NotifyType == NotifyType.Join)
{
ShareService.SharedMembers.Add(new ShareService.SharedMember { CharacterId = broadcast.CharacterId });
}
else if (broadcast.NotifyType == NotifyType.Leave)
{
ShareService.SharedMembers.RemoveAll(m => m.CharacterId == broadcast.CharacterId);
}
else if (broadcast.NotifyType == NotifyType.JoinViaParty)
{
ShareService.SharedMembers.Add(new ShareService.SharedMember { CharacterId = broadcast.CharacterId });
}
else if (broadcast.NotifyType == NotifyType.Rejoin)
{
ShareService.SharedMembers.Add(new ShareService.SharedMember { CharacterId = broadcast.CharacterId });
}
return Task.CompletedTask;
}
#endregion
public class SocketEventArgs : EventArgs
{
public bool Success { get; set; }
public string Message { get; set; } = "";
}
public class TokenCheckEventArgs : EventArgs
{
public bool TokenValid { get; set; }
public bool ShareCodeValid { get; set; }
}
}
}