#if !BESTHTTP_DISABLE_SIGNALR_CORE using System; using System.Collections.Generic; using BestHTTP.PlatformSupport.Memory; namespace BestHTTP.SignalRCore { public enum TransportTypes { #if !BESTHTTP_DISABLE_WEBSOCKET WebSocket, #endif LongPolling } public enum TransferModes { Binary, Text } public enum TransportStates { Initial, Connecting, Connected, Closing, Failed, Closed } /// /// Possible states of a HubConnection /// public enum ConnectionStates { Initial, Authenticating, Negotiating, Redirected, Reconnecting, Connected, CloseInitiated, Closed } /// /// States that a transport can goes trough as seen from 'outside'. /// public enum TransportEvents { /// /// Transport is selected to try to connect to the server /// SelectedToConnect, /// /// Transport failed to connect to the server. This event can occur after SelectedToConnect, when already connected and an error occurs it will be a ClosedWithError one. /// FailedToConnect, /// /// The transport successfully connected to the server. /// Connected, /// /// Transport gracefully terminated. /// Closed, /// /// Unexpected error occured and the transport can't recover from it. /// ClosedWithError } public interface ITransport { TransferModes TransferMode { get; } TransportTypes TransportType { get; } TransportStates State { get; } string ErrorReason { get; } event Action OnStateChanged; void StartConnect(); void StartClose(); void Send(BufferSegment bufferSegment); } public interface IEncoder { BufferSegment Encode(T value); T DecodeAs(BufferSegment buffer); object ConvertTo(Type toType, object obj); } public sealed class StreamItemContainer { public readonly long id; public List Items { get; private set; } public T LastAdded { get; private set; } public bool IsCanceled; public StreamItemContainer(long _id) { this.id = _id; this.Items = new List(); } public void AddItem(T item) { if (this.Items == null) this.Items = new List(); this.Items.Add(item); this.LastAdded = item; } } struct CallbackDescriptor { public readonly Type[] ParamTypes; public readonly Action Callback; public CallbackDescriptor(Type[] paramTypes, Action callback) { this.ParamTypes = paramTypes; this.Callback = callback; } } internal struct InvocationDefinition { public Action callback; public Type returnType; } internal sealed class Subscription { public List callbacks = new List(1); public void Add(Type[] paramTypes, Action callback) { this.callbacks.Add(new CallbackDescriptor(paramTypes, callback)); } public void Remove(Action callback) { int idx = -1; for (int i = 0; i < this.callbacks.Count && idx == -1; ++i) if (this.callbacks[i].Callback == callback) idx = i; if (idx != -1) this.callbacks.RemoveAt(idx); } } public sealed class HubOptions { /// /// When this is set to true, the plugin will skip the negotiation request if the PreferedTransport is WebSocket. Its default value is false. /// public bool SkipNegotiation { get; set; } /// /// The preferred transport to choose when more than one available. Its default value is TransportTypes.WebSocket. /// public TransportTypes PreferedTransport { get; set; } /// /// A ping message is only sent if the interval has elapsed without a message being sent. Its default value is 15 seconds. /// public TimeSpan PingInterval { get; set; } /// /// If the client doesn't see any message in this interval, considers the connection broken. Its default value is 30 seconds. /// public TimeSpan PingTimeoutInterval { get; set; } /// /// The maximum count of redirect negotiation result that the plugin will follow. Its default value is 100. /// public int MaxRedirects { get; set; } /// /// The maximum time that the plugin allowed to spend trying to connect. Its default value is 1 minute. /// public TimeSpan ConnectTimeout { get; set; } public HubOptions() { this.SkipNegotiation = false; #if !BESTHTTP_DISABLE_WEBSOCKET this.PreferedTransport = TransportTypes.WebSocket; #else this.PreferedTransport = TransportTypes.LongPolling; #endif this.PingInterval = TimeSpan.FromSeconds(15); this.PingTimeoutInterval = TimeSpan.FromSeconds(30); this.MaxRedirects = 100; this.ConnectTimeout = TimeSpan.FromSeconds(60); } } public interface IRetryPolicy { /// /// This function must return with a delay time to wait until a new connection attempt, or null to do not do another one. /// TimeSpan? GetNextRetryDelay(RetryContext context); } public struct RetryContext { /// /// Previous reconnect attempts. A successful connection sets it back to zero. /// public uint PreviousRetryCount; /// /// Elapsed time since the original connection error. /// public TimeSpan ElapsedTime; /// /// String representation of the connection error. /// public string RetryReason; } public sealed class DefaultRetryPolicy : IRetryPolicy { private static TimeSpan?[] DefaultBackoffTimes = new TimeSpan?[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30), null }; TimeSpan?[] backoffTimes; public DefaultRetryPolicy() { this.backoffTimes = DefaultBackoffTimes; } public DefaultRetryPolicy(TimeSpan?[] customBackoffTimes) { this.backoffTimes = customBackoffTimes; } public TimeSpan? GetNextRetryDelay(RetryContext context) { if (context.PreviousRetryCount >= this.backoffTimes.Length) return null; return this.backoffTimes[context.PreviousRetryCount]; } } } #endif