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; } static NSMutableUrlRequest CreateUrlRequest(string url, string accept, string? referrer = null) { var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url)); var request = new NSMutableUrlRequest(uri) { [AcceptHeader] = accept }; if (!string.IsNullOrEmpty(referrer)) { request[ReferrerHeader] = referrer; } return request; } 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 request = CreateUrlRequest(url, Accept ?? AcceptHttp, 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 request = CreateUrlRequest(url, Accept ?? AcceptImage, 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, string data, string contentType = ContentJson, CancellationToken token = default) { return RequestAsync(url, request => { request[ContentTypeHeader] = contentType; request.Body = NSData.FromString(data, NSStringEncoding.UTF8); }, token); } public Task> PostAsync(string url, MultipartFormData[] forms, CancellationToken token = default) { return RequestAsync(url, request => { var body = new NSMultipartFormData(); request[ContentTypeHeader] = body.ContentType; foreach (var form in forms) { if (form is FieldFormData field) { if (!string.IsNullOrEmpty(field.Value)) { body.AddTextField(field.Name, field.Value); } } else if (form is FileFormData file) { body.AddDataField(file.Name, file.Stream, file.FileName, file.MimeType); } } body.AddEnd(); request.Body = body.Data; }, token); } public Task> RequestAsync(string url, Action? prepare = null, CancellationToken token = default) { var request = CreateUrlRequest(url, Accept ?? AcceptJson, Referrer); request.HttpMethod = "POST"; 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.OnResponded(httpResponse)) { completionHandler(NSUrlSessionResponseDisposition.Allow); } else { completionHandler(NSUrlSessionResponseDisposition.Cancel); networkTask.SetException(new TaskCanceledException($"Cancelled on task responded: {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); } }