using CoreGraphics; using Foundation; namespace Blahblah.Library.Network; partial class NetworkHelper : NSObject, INSUrlSessionDataDelegate { static bool IsResponseSuccess(NSHttpUrlResponse response) { return (int)response.StatusCode is >= 200 and < 300; } NSUrlSession? urlSession; readonly Dictionary tasks = new(); private NetworkHelper(int timeout, bool useCookie, bool waitsConnectivity = true, Dictionary? additionalHeaders = null) { var configuration = NSUrlSessionConfiguration.EphemeralSessionConfiguration; configuration.WaitsForConnectivity = waitsConnectivity; configuration.RequestCachePolicy = NSUrlRequestCachePolicy.UseProtocolCachePolicy; configuration.TimeoutIntervalForRequest = timeout; if (additionalHeaders != null) { configuration.HttpAdditionalHeaders = NSDictionary.FromObjectsAndKeys( additionalHeaders.Values.Select(v => FromObject(v)).ToArray(), additionalHeaders.Keys.Select(k => FromObject(k)).ToArray()); } if (useCookie) { configuration.HttpCookieStorage = NSHttpCookieStorage.SharedStorage; } else { configuration.HttpShouldSetCookies = false; } if (proxyHost != null && proxyPort != null) { configuration.StrongConnectionProxyDictionary = new ProxyConfigurationDictionary { HttpEnable = true, HttpsProxyHost = proxyHost, HttpsProxyPort = proxyPort, HttpProxyHost = proxyHost, HttpProxyPort = proxyPort }; } urlSession = NSUrlSession.FromConfiguration(configuration, this, null); } protected override void Dispose(bool disposing) { if (disposing) { lock (sync) { foreach (var task in tasks.Values) { task.Dispose(); } } if (urlSession != null) { urlSession.FinishTasksAndInvalidate(); urlSession.Dispose(); urlSession = null; } } base.Dispose(disposing); } public partial Task> GetContentAsync(string url, StringHandler? process, CancellationToken token) { var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url)); var request = new NSMutableUrlRequest(uri) { [AcceptHeader] = Accept ?? AcceptHttp, [ReferrerHeader] = Referrer }; var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null"); var taskSource = new TaskCompletionSource>(); var stringTask = new StringTask(url, taskSource, token) { Process = process }; lock (sync) { tasks.Add(task, stringTask); } task.Resume(); return taskSource.Task; } public Task> GetImageAsync(string url, string filePath, StepHandler? step = null, CancellationToken token = default) { var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url)); var request = new NSMutableUrlRequest(uri) { [AcceptHeader] = Accept ?? AcceptImage, [ReferrerHeader] = Referrer }; var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null"); var taskSource = new TaskCompletionSource>(); var imageTask = new ImageTask(url, filePath, step != null, taskSource, token) { Step = step }; lock (sync) { tasks.Add(task, imageTask); } task.Resume(); return taskSource.Task; } public Task> PostAsync(string url, Action? prepare = null, CancellationToken token = default) { var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url)); var request = new NSMutableUrlRequest(uri) { HttpMethod = "POST", [AcceptHeader] = Accept ?? AcceptImage, [ReferrerHeader] = Referrer }; prepare?.Invoke(request); var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null"); var taskSource = new TaskCompletionSource>(); var stringTask = new StringTask(url, taskSource, token); lock (sync) { tasks.Add(task, stringTask); } task.Resume(); return taskSource.Task; } bool TryGetTask(NSUrlSessionTask task, out NetworkTask? networkTask) { bool got; lock (sync) { got = tasks.TryGetValue(task, out networkTask); } return got; } [Export("URLSession:dataTask:didReceiveResponse:completionHandler:")] public void DidReceiveResponse(NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action completionHandler) { if (!TryGetTask(dataTask, out NetworkTask? networkTask) || networkTask == null) { completionHandler(NSUrlSessionResponseDisposition.Cancel); return; } if (networkTask.IsCancelled) { completionHandler(NSUrlSessionResponseDisposition.Cancel); networkTask.SetException(new TaskCanceledException($"Cancelled on response received: {dataTask.CurrentRequest?.Url}", null, networkTask.Token)); return; } if (response is NSHttpUrlResponse httpResponse) { if (IsResponseSuccess(httpResponse)) { if (networkTask.OnResponsed(httpResponse)) { completionHandler(NSUrlSessionResponseDisposition.Allow); } else { completionHandler(NSUrlSessionResponseDisposition.Cancel); networkTask.SetException(new TaskCanceledException($"Cancelled on task responsed: {dataTask.CurrentRequest?.Url}", null, networkTask.Token)); } } else { completionHandler(NSUrlSessionResponseDisposition.Cancel); networkTask.SetException(new HttpResponseException((int)httpResponse.StatusCode, httpResponse.Url.AbsoluteString ?? networkTask.Url)); } } else { completionHandler(NSUrlSessionResponseDisposition.Cancel); networkTask.SetException(new InvalidDataException($"Response is not instance of but <{response?.GetType()}>")); } } [Export("URLSession:dataTask:didReceiveData:")] public void DidReceiveData(NSUrlSession session, NSUrlSessionDataTask dataTask, NSData data) { if (!TryGetTask(dataTask, out NetworkTask? networkTask) || networkTask == null) { return; } if (networkTask.IsCancelled) { networkTask.SetException(new TaskCanceledException($"Cancelled on data received: {dataTask.CurrentRequest?.Url}", null, networkTask.Token)); return; } if (networkTask.Data == null) { networkTask.SetException(new InvalidDataException(" is null")); return; } networkTask.Data.AppendData(data); networkTask.OnReceived((int)data.Length); } [Export("URLSession:task:didCompleteWithError:")] public void DidCompleteWithError(NSUrlSession session, NSUrlSessionTask task, NSError error) { if (!TryGetTask(task, out NetworkTask? networkTask) || networkTask == null) { return; } if (networkTask.IsCancelled) { networkTask.SetException(new TaskCanceledException($"Cancelled on completed: {task.CurrentRequest?.Url}", null, networkTask.Token)); return; } if (networkTask.Data == null) { networkTask.SetException(new InvalidDataException(" is null")); return; } if (error != null) { networkTask.SetException(new Exception(error.ToString())); return; } networkTask.SetCompleted(task.Response as NSHttpUrlResponse); } }