using AyCode.Core.Extensions; using MessagePack; using AyCode.Core.Interfaces; using AyCode.Core.Compression; using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute; using STJIgnore = System.Text.Json.Serialization.JsonIgnoreAttribute; namespace AyCode.Services.SignalRs; public class IdMessage { public List Ids { get; private set; } public IdMessage() { Ids = []; } /// /// Creates IdMessage with multiple parameters serialized directly as JSON. /// Each parameter is serialized independently without array wrapping. /// Use object[] explicitly to pass multiple parameters. /// public IdMessage(object[] ids) { Ids = new List(ids.Length); for (var i = 0; i < ids.Length; i++) { Ids.Add(ids[i].ToJson()); } } /// /// Creates IdMessage with a single parameter serialized as JSON. /// Collections (List, Array, etc.) are serialized as a single JSON array. /// public IdMessage(object id) { Ids = new List(1) { id.ToJson() }; } /// /// Creates IdMessage with multiple Guid parameters. /// Each Guid is serialized as a separate Id entry. /// public IdMessage(IEnumerable ids) { var idsArray = ids as Guid[] ?? ids.ToArray(); Ids = new List(idsArray.Length); for (var i = 0; i < idsArray.Length; i++) { Ids.Add(idsArray[i].ToJson()); } } public override string ToString() => string.Join("; ", Ids); } [MessagePackObject] public class SignalPostJsonMessage { [Key(0)] public string PostDataJson { get; set; } = ""; public SignalPostJsonMessage() { } protected SignalPostJsonMessage(string postDataJson) => PostDataJson = postDataJson; } [MessagePackObject(AllowPrivate = false)] public class SignalPostJsonDataMessage : SignalPostJsonMessage, ISignalPostMessage { [IgnoreMember] [JsonIgnore] [STJIgnore] private TPostDataType? _postData; [IgnoreMember] [JsonIgnore] [STJIgnore] public TPostDataType PostData { get => _postData ??= PostDataJson.JsonTo()!; private init { _postData = value; PostDataJson = _postData.ToJson(); } } public SignalPostJsonDataMessage() : base() { } public SignalPostJsonDataMessage(TPostDataType postData) => PostData = postData; public SignalPostJsonDataMessage(string postDataJson) : base(postDataJson) { } } [MessagePackObject] public class SignalPostMessage(TPostData postData) : ISignalPostMessage { [Key(0)] public TPostData? PostData { get; set; } = postData; } public interface ISignalPostMessage : ISignalRMessage { TPostData? PostData { get; } } [MessagePackObject] public class SignalRequestByIdMessage(Guid id) : ISignalRequestMessage, IId { [Key(0)] public Guid Id { get; set; } = id; } public interface ISignalRequestMessage : ISignalRMessage { TRequestId Id { get; set; } } public interface ISignalRMessage { } public interface ISignalResponseMessage : ISignalRMessage { int MessageTag { get; set; } SignalResponseStatus Status { get; set; } } public enum SignalResponseStatus : byte { Error = 0, Success = 5 } /// /// Signal response message with lazy deserialization support. /// Used for callback-based response handling. /// [MessagePackObject(AllowPrivate = false)] public sealed class SignalResponseMessage : ISignalResponseMessage { [IgnoreMember] [JsonIgnore] [STJIgnore] private TResponseData? _responseData; [IgnoreMember] [JsonIgnore] [STJIgnore] private bool _isDeserialized; [Key(0)] public int MessageTag { get; set; } [Key(1)] public SignalResponseStatus Status { get; set; } [Key(2)] public string? ResponseDataJson { get; set; } [IgnoreMember] [JsonIgnore] [STJIgnore] public TResponseData? ResponseData { get { if (!_isDeserialized) { _isDeserialized = true; _responseData = ResponseDataJson != null ? ResponseDataJson.JsonTo() : default; } return _responseData; } set { _isDeserialized = true; _responseData = value; ResponseDataJson = value?.ToJson(); } } public SignalResponseMessage() { } public SignalResponseMessage(int messageTag, SignalResponseStatus status) { MessageTag = messageTag; Status = status; } public SignalResponseMessage(int messageTag, SignalResponseStatus status, TResponseData? responseData) : this(messageTag, status) => ResponseData = responseData; public SignalResponseMessage(int messageTag, SignalResponseStatus status, string? responseDataJson) : this(messageTag, status) => ResponseDataJson = responseDataJson; } /// /// Unified signal response message that supports both JSON and Binary serialization. /// JSON mode uses Brotli compression for reduced payload size. /// Optimized: decompression is performed only once and cached. /// public sealed class SignalResponseDataMessage : ISignalResponseMessage { public int MessageTag { get; set; } public SignalResponseStatus Status { get; set; } public AcSerializerType DataSerializerType { get; set; } public byte[]? ResponseDataBin { get; set; } [JsonIgnore] [STJIgnore] private string? _cachedJson; [JsonIgnore] [STJIgnore] private object? _cachedResponseData; public SignalResponseDataMessage() { } public SignalResponseDataMessage(int messageTag, SignalResponseStatus status) { MessageTag = messageTag; Status = status; } public SignalResponseDataMessage(int messageTag, SignalResponseStatus status, object? responseData, AcSerializerOptions serializerOptions) : this(messageTag, status) { DataSerializerType = serializerOptions.SerializerType; if (responseData == null) { ResponseDataBin = null; return; } if (serializerOptions.SerializerType == AcSerializerType.Binary) { if (responseData is byte[] byteData) { ResponseDataBin = byteData; return; } var binaryOptions = serializerOptions as AcBinarySerializerOptions ?? AcBinarySerializerOptions.Default; ResponseDataBin = responseData.ToBinary(binaryOptions); } else { string json; if (responseData is string strData) { var trimmed = strData.Trim(); if (trimmed.Length > 1 && (trimmed[0] == '{' || trimmed[0] == '[') && (trimmed[^1] == '}' || trimmed[^1] == ']')) json = strData; else { var jsonOptions = serializerOptions as AcJsonSerializerOptions ?? AcJsonSerializerOptions.Default; json = responseData.ToJson(jsonOptions); } } else { var jsonOptions = serializerOptions as AcJsonSerializerOptions ?? AcJsonSerializerOptions.Default; json = responseData.ToJson(jsonOptions); } ResponseDataBin = BrotliHelper.Compress(json); } } /// /// Deserializes the ResponseData to the specified type. /// Uses cached decompressed JSON for repeated calls. /// public T? GetResponseData() { if (_cachedResponseData != null) return (T)_cachedResponseData; if (ResponseDataBin == null) return default; if (DataSerializerType == AcSerializerType.Binary) return (T)(_cachedResponseData = ResponseDataBin.BinaryTo()); _cachedJson ??= BrotliHelper.DecompressToString(ResponseDataBin); return (T)(_cachedResponseData = _cachedJson.JsonTo()); } } public interface IAcSignalRHubClient : IAcSignalRHubBase { Task SendMessageToServerAsync(int messageTag, ISignalRMessage? message, int? requestId); }