| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// Provides access to the |
| /// [Foundation URL Loading System](https://developer.apple.com/documentation/foundation/url_loading_system). |
| /// |
| /// For example: |
| /// ``` |
| /// void main() { |
| /// final url = Uri.https('www.example.com', '/'); |
| /// final session = URLSession.sharedSession(); |
| /// final task = session.dataTaskWithCompletionHandler( |
| /// URLRequest.fromUrl(url), |
| /// (data, response, error) { |
| /// if (error == null) { |
| /// if (response != null && response.statusCode == 200) { |
| /// print(response); // Do something with the response. |
| /// return; |
| /// } |
| /// } |
| /// print(error); // Handle errors. |
| /// }); |
| /// task.resume(); |
| /// } |
| /// ``` |
| library; |
| |
| import 'dart:async'; |
| import 'dart:ffi'; |
| import 'dart:isolate'; |
| import 'dart:math'; |
| import 'dart:typed_data'; |
| |
| import 'package:async/async.dart'; |
| import 'package:ffi/ffi.dart'; |
| |
| import 'native_cupertino_bindings.dart' as ncb; |
| import 'utils.dart'; |
| |
| abstract class _ObjectHolder<T extends ncb.NSObject> { |
| final T _nsObject; |
| |
| _ObjectHolder(this._nsObject); |
| |
| @override |
| bool operator ==(Object other) { |
| if (other is _ObjectHolder) { |
| return _nsObject == other._nsObject; |
| } |
| return false; |
| } |
| |
| @override |
| int get hashCode => _nsObject.hashCode; |
| } |
| |
| /// Settings for controlling whether cookies will be accepted. |
| /// |
| /// See [HTTPCookieAcceptPolicy](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1408933-httpcookieacceptpolicy). |
| enum HTTPCookieAcceptPolicy { |
| httpCookieAcceptPolicyAlways, |
| httpCookieAcceptPolicyNever, |
| httpCookieAcceptPolicyOnlyFromMainDocumentDomain, |
| } |
| |
| /// Controls how response data is cached. |
| /// |
| /// See [URLRequestCachePolicy](https://developer.apple.com/documentation/foundation/nsurlrequestcachepolicy). |
| enum URLRequestCachePolicy { |
| useProtocolCachePolicy, |
| reloadIgnoringLocalCacheData, |
| returnCacheDataElseLoad, |
| returnCacheDataDontLoad, |
| reloadIgnoringLocalAndRemoteCacheData, |
| reloadRevalidatingCacheData, |
| } |
| |
| // Controls how multipath TCP should be used. |
| // |
| // See [NSURLSessionMultipathServiceType](https://developer.apple.com/documentation/foundation/nsurlsessionmultipathservicetype). |
| enum URLSessionMultipathServiceType { |
| multipathServiceTypeNone, |
| multipathServiceTypeHandover, |
| multipathServiceTypeInteractive, |
| multipathServiceTypeAggregate, |
| } |
| |
| /// Controls how [URLSessionTask] execute will proceed after the response is |
| /// received. |
| /// |
| /// See [NSURLSessionResponseDisposition](https://developer.apple.com/documentation/foundation/nsurlsessionresponsedisposition). |
| enum URLSessionResponseDisposition { |
| urlSessionResponseCancel, |
| urlSessionResponseAllow, |
| urlSessionResponseBecomeDownload, |
| urlSessionResponseBecomeStream |
| } |
| |
| /// Provides in indication to the operating system on what type of requests |
| /// are being sent. |
| /// |
| /// See [NSURLRequestNetworkServiceType](https://developer.apple.com/documentation/foundation/nsurlrequestnetworkservicetype). |
| enum URLRequestNetworkService { |
| networkServiceTypeDefault, |
| networkServiceTypeVoIP, |
| networkServiceTypeVideo, |
| networkServiceTypeBackground, |
| networkServiceTypeVoice, |
| networkServiceTypeResponsiveData, |
| networkServiceTypeAVStreaming, |
| networkServiceTypeResponsiveAV, |
| networkServiceTypeCallSignaling |
| } |
| |
| /// The type of a WebSocket message i.e. text or data. |
| /// |
| /// See [NSURLSessionWebSocketMessageType](https://developer.apple.com/documentation/foundation/nsurlsessionwebsocketmessagetype) |
| enum URLSessionWebSocketMessageType { |
| urlSessionWebSocketMessageTypeData, |
| urlSessionWebSocketMessageTypeString, |
| } |
| |
| ncb.NSInputStream _streamToNSInputStream(Stream<List<int>> stream) { |
| const maxReadAheadSize = 4096; |
| final queue = StreamQueue(stream); |
| final port = ReceivePort(); |
| final inputStream = ncb.CUPHTTPStreamToNSInputStreamAdapter.alloc(helperLibs) |
| .initWithPort_(port.sendPort.nativePort); |
| |
| late StreamSubscription<dynamic> s; |
| // Messages from `CUPHTTPStreamToNSInputStreamAdapter` indicate that the task |
| // is attempting to read more data but there is none available. |
| s = port.listen((_) async { |
| if (inputStream.streamStatus == ncb.NSStreamStatus.NSStreamStatusClosed) { |
| return; |
| } |
| |
| // Prevent multiple executions of this code to be in flight at once. |
| s.pause(); |
| while (await queue.hasNext && |
| inputStream.streamStatus != ncb.NSStreamStatus.NSStreamStatusClosed) { |
| late final List<int> next; |
| try { |
| next = await queue.next; |
| } catch (e) { |
| inputStream.setError_(Error.fromCustomDomain('DartError', 0, |
| localizedDescription: e.toString()) |
| ._nsObject); |
| break; |
| } |
| // In practice the read length request will be large (>1MB) so, |
| // instead of adding that much data, try to keep the buffer |
| // at least `maxReadAheadSize`. |
| if (inputStream.addData_(Data.fromList(next)._nsObject) > |
| maxReadAheadSize) { |
| break; |
| } |
| } |
| if (!await queue.hasNext) { |
| inputStream.setDone(); |
| } else { |
| s.resume(); |
| } |
| }); |
| return inputStream; |
| } |
| |
| /// Information about a failure. |
| /// |
| /// See [NSError](https://developer.apple.com/documentation/foundation/nserror) |
| class Error extends _ObjectHolder<ncb.NSError> implements Exception { |
| Error._(super.c); |
| |
| /// Create an Error from a custom domain. |
| factory Error.fromCustomDomain(String domain, int code, |
| {String? localizedDescription}) { |
| final d = ncb.NSMutableDictionary.alloc(linkedLibs).init(); |
| |
| if (localizedDescription != null) { |
| d.setObject_forKey_( |
| localizedDescription.toNSString(linkedLibs), |
| ncb.NSString.castFromPointer( |
| linkedLibs, linkedLibs.NSLocalizedDescriptionKey), |
| ); |
| } |
| final e = ncb.NSError.alloc(linkedLibs).initWithDomain_code_userInfo_( |
| domain.toNSString(linkedLibs).pointer, code, d); |
| return Error._(e); |
| } |
| |
| /// The numeric code for the error e.g. -1003 (kCFURLErrorCannotFindHost). |
| /// |
| /// The interpretation of this code will depend on the domain of the error |
| /// which, for URL loading, will usually be |
| /// [`kCFErrorDomainCFNetwork`](https://developer.apple.com/documentation/cfnetwork/kcferrordomaincfnetwork). |
| /// |
| /// See [NSError.code](https://developer.apple.com/documentation/foundation/nserror/1409165-code) |
| int get code => _nsObject.code; |
| |
| /// The error domain, for example `"NSPOSIXErrorDomain"`. |
| /// |
| /// See [NSError.domain](https://developer.apple.com/documentation/foundation/nserror/1413924-domain) |
| String get domain => |
| ncb.NSString.castFromPointer(linkedLibs, _nsObject.domain).toString(); |
| |
| /// A description of the error in the current locale e.g. |
| /// 'A server with the specified hostname could not be found.' |
| /// |
| /// See [NSError.localizedDescription](https://developer.apple.com/documentation/foundation/nserror/1414418-localizeddescription) |
| String? get localizedDescription => |
| toStringOrNull(_nsObject.localizedDescription); |
| |
| /// An explanation of the reason for the error in the current locale. |
| /// |
| /// See [NSError.localizedFailureReason](https://developer.apple.com/documentation/foundation/nserror/1412752-localizedfailurereason) |
| String? get localizedFailureReason => |
| toStringOrNull(_nsObject.localizedFailureReason); |
| |
| /// An explanation of how to fix the error in the current locale. |
| /// |
| /// See [NSError.localizedRecoverySuggestion](https://developer.apple.com/documentation/foundation/nserror/1407500-localizedrecoverysuggestion) |
| String? get localizedRecoverySuggestion => |
| toStringOrNull(_nsObject.localizedRecoverySuggestion); |
| |
| @override |
| String toString() => '[Error ' |
| 'domain=$domain ' |
| 'code=$code ' |
| 'localizedDescription=$localizedDescription ' |
| 'localizedFailureReason=$localizedFailureReason ' |
| 'localizedRecoverySuggestion=$localizedRecoverySuggestion ' |
| ']'; |
| } |
| |
| /// A cache for [URLRequest]s. Used by [URLSessionConfiguration.cache]. |
| /// |
| /// See [NSURLCache](https://developer.apple.com/documentation/foundation/nsurlcache) |
| class URLCache extends _ObjectHolder<ncb.NSURLCache> { |
| URLCache._(super.c); |
| |
| /// The default URLCache. |
| /// |
| /// See [NSURLCache.sharedURLCache](https://developer.apple.com/documentation/foundation/nsurlcache/1413377-sharedurlcache) |
| static URLCache? get sharedURLCache { |
| final sharedCache = ncb.NSURLCache.getSharedURLCache(linkedLibs); |
| return sharedCache == null ? null : URLCache._(sharedCache); |
| } |
| |
| /// Create a new [URLCache] with the given memory and disk cache sizes. |
| /// |
| /// [memoryCapacity] and [diskCapacity] are specified in bytes. |
| /// |
| /// [directory] is the file system location where the disk cache will be |
| /// stored. If `null` then the default directory will be used. |
| /// |
| /// See [NSURLCache initWithMemoryCapacity:diskCapacity:directoryURL:](https://developer.apple.com/documentation/foundation/nsurlcache/3240612-initwithmemorycapacity) |
| factory URLCache.withCapacity( |
| {int memoryCapacity = 0, int diskCapacity = 0, Uri? directory}) => |
| URLCache._(ncb.NSURLCache.alloc(linkedLibs) |
| .initWithMemoryCapacity_diskCapacity_directoryURL_(memoryCapacity, |
| diskCapacity, directory == null ? null : uriToNSURL(directory))); |
| } |
| |
| /// Controls the behavior of a URLSession. |
| /// |
| /// See [NSURLSessionConfiguration](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration) |
| class URLSessionConfiguration |
| extends _ObjectHolder<ncb.NSURLSessionConfiguration> { |
| // A configuration created with |
| // [`backgroundSessionConfigurationWithIdentifier`](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1407496-backgroundsessionconfigurationwi) |
| final bool _isBackground; |
| |
| URLSessionConfiguration._(super.c, {required bool isBackground}) |
| : _isBackground = isBackground; |
| |
| /// A configuration suitable for performing HTTP uploads and downloads in |
| /// the background. |
| /// |
| /// See [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1407496-backgroundsessionconfigurationwi) |
| factory URLSessionConfiguration.backgroundSession(String identifier) => |
| URLSessionConfiguration._( |
| ncb.NSURLSessionConfiguration |
| .backgroundSessionConfigurationWithIdentifier_( |
| linkedLibs, identifier.toNSString(linkedLibs)), |
| isBackground: true); |
| |
| /// A configuration that uses caching and saves cookies and credentials. |
| /// |
| /// See [NSURLSessionConfiguration defaultSessionConfiguration](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411560-defaultsessionconfiguration) |
| factory URLSessionConfiguration.defaultSessionConfiguration() => |
| URLSessionConfiguration._( |
| ncb.NSURLSessionConfiguration.castFrom( |
| ncb.NSURLSessionConfiguration.getDefaultSessionConfiguration( |
| linkedLibs)!), |
| isBackground: false); |
| |
| /// A session configuration that uses no persistent storage for caches, |
| /// cookies, or credentials. |
| /// |
| /// See [NSURLSessionConfiguration ephemeralSessionConfiguration](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1410529-ephemeralsessionconfiguration) |
| factory URLSessionConfiguration.ephemeralSessionConfiguration() => |
| URLSessionConfiguration._( |
| ncb.NSURLSessionConfiguration.castFrom( |
| ncb.NSURLSessionConfiguration.getEphemeralSessionConfiguration( |
| linkedLibs)!), |
| isBackground: false); |
| |
| /// Whether connections over a cellular network are allowed. |
| /// |
| /// See [NSURLSessionConfiguration.allowsCellularAccess](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1409406-allowscellularaccess) |
| bool get allowsCellularAccess => _nsObject.allowsCellularAccess; |
| set allowsCellularAccess(bool value) => |
| _nsObject.allowsCellularAccess = value; |
| |
| /// Whether connections are allowed when the user has selected Low Data Mode. |
| /// |
| /// See [NSURLSessionConfiguration.allowsConstrainedNetworkAccess](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/3235751-allowsconstrainednetworkaccess) |
| bool get allowsConstrainedNetworkAccess => |
| _nsObject.allowsConstrainedNetworkAccess; |
| set allowsConstrainedNetworkAccess(bool value) => |
| _nsObject.allowsConstrainedNetworkAccess = value; |
| |
| /// Whether connections are allowed over expensive networks. |
| /// |
| /// See [NSURLSessionConfiguration.allowsExpensiveNetworkAccess](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/3235752-allowsexpensivenetworkaccess) |
| bool get allowsExpensiveNetworkAccess => |
| _nsObject.allowsExpensiveNetworkAccess; |
| set allowsExpensiveNetworkAccess(bool value) => |
| _nsObject.allowsExpensiveNetworkAccess = value; |
| |
| /// The [URLCache] used to cache the results of [URLSessionTask]s. |
| /// |
| /// A value of `nil` indicates that no cache will be used. |
| /// |
| /// See [NSURLSessionConfiguration.URLCache](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1410148-urlcache) |
| URLCache? get cache => |
| _nsObject.URLCache == null ? null : URLCache._(_nsObject.URLCache!); |
| set cache(URLCache? cache) => _nsObject.URLCache = cache?._nsObject; |
| |
| /// Whether background tasks can be delayed by the system. |
| /// |
| /// See [NSURLSessionConfiguration.discretionary](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411552-discretionary) |
| bool get discretionary => _nsObject.discretionary; |
| set discretionary(bool value) => _nsObject.discretionary = value; |
| |
| /// Additional headers to send with each request. |
| /// |
| /// Note that the getter for this field returns a **copy** of the headers. |
| /// |
| /// See [NSURLSessionConfiguration.HTTPAdditionalHeaders](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411532-httpadditionalheaders) |
| Map<String, String>? get httpAdditionalHeaders { |
| if (_nsObject.HTTPAdditionalHeaders case var additionalHeaders?) { |
| final headers = ncb.NSDictionary.castFrom(additionalHeaders); |
| return stringDictToMap(headers); |
| } |
| return null; |
| } |
| |
| set httpAdditionalHeaders(Map<String, String>? headers) { |
| if (headers == null) { |
| _nsObject.HTTPAdditionalHeaders = null; |
| return; |
| } |
| final d = ncb.NSMutableDictionary.alloc(linkedLibs).init(); |
| headers.forEach((key, value) { |
| d.setObject_forKey_( |
| value.toNSString(linkedLibs), key.toNSString(linkedLibs)); |
| }); |
| _nsObject.HTTPAdditionalHeaders = d; |
| } |
| |
| /// What policy to use when deciding whether to accept cookies. |
| /// |
| /// See [NSURLSessionConfiguration.HTTPCookieAcceptPolicy](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1408933-httpcookieacceptpolicy). |
| HTTPCookieAcceptPolicy get httpCookieAcceptPolicy => |
| HTTPCookieAcceptPolicy.values[_nsObject.HTTPCookieAcceptPolicy]; |
| set httpCookieAcceptPolicy(HTTPCookieAcceptPolicy value) => |
| _nsObject.HTTPCookieAcceptPolicy = value.index; |
| |
| /// The maximum number of connections that a URLSession can have open to the |
| /// same host. |
| // |
| /// See [NSURLSessionConfiguration.HTTPMaximumConnectionsPerHost](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1407597-httpmaximumconnectionsperhost). |
| int get httpMaximumConnectionsPerHost => |
| _nsObject.HTTPMaximumConnectionsPerHost; |
| set httpMaximumConnectionsPerHost(int value) => |
| _nsObject.HTTPMaximumConnectionsPerHost = value; |
| |
| /// Whether requests should include cookies from the cookie store. |
| /// |
| /// See [NSURLSessionConfiguration.HTTPShouldSetCookies](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411589-httpshouldsetcookies) |
| bool get httpShouldSetCookies => _nsObject.HTTPShouldSetCookies; |
| set httpShouldSetCookies(bool value) => |
| _nsObject.HTTPShouldSetCookies = value; |
| |
| /// Whether to use [HTTP pipelining](https://en.wikipedia.org/wiki/HTTP_pipelining). |
| /// |
| /// See [NSURLSessionConfiguration.HTTPShouldUsePipelining](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411657-httpshouldusepipelining) |
| bool get httpShouldUsePipelining => _nsObject.HTTPShouldUsePipelining; |
| set httpShouldUsePipelining(bool value) => |
| _nsObject.HTTPShouldUsePipelining = value; |
| |
| /// What type of Multipath TCP connections to use. |
| /// |
| /// See [NSURLSessionConfiguration.multipathServiceType](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/2875967-multipathservicetype) |
| URLSessionMultipathServiceType get multipathServiceType => |
| URLSessionMultipathServiceType.values[_nsObject.multipathServiceType]; |
| set multipathServiceType(URLSessionMultipathServiceType value) => |
| _nsObject.multipathServiceType = value.index; |
| |
| /// Provides in indication to the operating system on what type of requests |
| /// are being sent. |
| /// |
| /// See [NSURLSessionConfiguration.networkServiceType](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411606-networkservicetype). |
| URLRequestNetworkService get networkServiceType => |
| URLRequestNetworkService.values[_nsObject.networkServiceType]; |
| set networkServiceType(URLRequestNetworkService value) => |
| _nsObject.networkServiceType = value.index; |
| |
| /// Controls how to deal with response caching. |
| /// |
| /// See [NSURLSessionConfiguration.requestCachePolicy](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411655-requestcachepolicy) |
| URLRequestCachePolicy get requestCachePolicy => |
| URLRequestCachePolicy.values[_nsObject.requestCachePolicy]; |
| set requestCachePolicy(URLRequestCachePolicy value) => |
| _nsObject.requestCachePolicy = value.index; |
| |
| /// Whether the app should be resumed when background tasks complete. |
| /// |
| /// See [NSURLSessionConfiguration.sessionSendsLaunchEvents](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1617174-sessionsendslaunchevents) |
| bool get sessionSendsLaunchEvents => _nsObject.sessionSendsLaunchEvents; |
| set sessionSendsLaunchEvents(bool value) => |
| _nsObject.sessionSendsLaunchEvents = value; |
| |
| /// Whether connections will be preserved if the app moves to the background. |
| /// |
| /// See [NSURLSessionConfiguration.shouldUseExtendedBackgroundIdleMode](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1409517-shoulduseextendedbackgroundidlem) |
| bool get shouldUseExtendedBackgroundIdleMode => |
| _nsObject.shouldUseExtendedBackgroundIdleMode; |
| set shouldUseExtendedBackgroundIdleMode(bool value) => |
| _nsObject.shouldUseExtendedBackgroundIdleMode = value; |
| |
| /// The timeout interval if data is not received. |
| /// |
| /// See [NSURLSessionConfiguration.timeoutIntervalForRequest](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1408259-timeoutintervalforrequest) |
| Duration get timeoutIntervalForRequest => Duration( |
| microseconds: |
| (_nsObject.timeoutIntervalForRequest * Duration.microsecondsPerSecond) |
| .round()); |
| |
| set timeoutIntervalForRequest(Duration interval) { |
| _nsObject.timeoutIntervalForRequest = |
| interval.inMicroseconds.toDouble() / Duration.microsecondsPerSecond; |
| } |
| |
| /// Whether tasks should wait for connectivity or fail immediately. |
| /// |
| /// See [NSURLSessionConfiguration.waitsForConnectivity](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/2908812-waitsforconnectivity) |
| bool get waitsForConnectivity => _nsObject.waitsForConnectivity; |
| set waitsForConnectivity(bool value) => |
| _nsObject.waitsForConnectivity = value; |
| |
| @override |
| String toString() => '[URLSessionConfiguration ' |
| 'allowsCellularAccess=$allowsCellularAccess ' |
| 'allowsConstrainedNetworkAccess=$allowsConstrainedNetworkAccess ' |
| 'allowsExpensiveNetworkAccess=$allowsExpensiveNetworkAccess ' |
| 'discretionary=$discretionary ' |
| 'httpAdditionalHeaders=$httpAdditionalHeaders ' |
| 'httpCookieAcceptPolicy=$httpCookieAcceptPolicy ' |
| 'httpShouldSetCookies=$httpShouldSetCookies ' |
| 'httpMaximumConnectionsPerHost=$httpMaximumConnectionsPerHost ' |
| 'httpShouldUsePipelining=$httpShouldUsePipelining ' |
| 'requestCachePolicy=$requestCachePolicy ' |
| 'sessionSendsLaunchEvents=$sessionSendsLaunchEvents ' |
| 'shouldUseExtendedBackgroundIdleMode=' |
| '$shouldUseExtendedBackgroundIdleMode ' |
| 'timeoutIntervalForRequest=$timeoutIntervalForRequest ' |
| 'waitsForConnectivity=$waitsForConnectivity' |
| ']'; |
| } |
| |
| /// A container for byte data. |
| /// |
| /// See [NSData](https://developer.apple.com/documentation/foundation/nsdata) |
| class Data extends _ObjectHolder<ncb.NSData> { |
| Data._(super.c); |
| |
| /// A new [Data] from an existing one. |
| /// |
| /// See [NSData dataWithData:](https://developer.apple.com/documentation/foundation/nsdata/1547230-datawithdata) |
| factory Data.fromData(Data d) => |
| Data._(ncb.NSData.dataWithData_(linkedLibs, d._nsObject)); |
| |
| /// A new [Data] object containing the given bytes. |
| factory Data.fromList(List<int> l) { |
| final buffer = calloc<Uint8>(l.length); |
| try { |
| buffer.asTypedList(l.length).setAll(0, l); |
| |
| final data = |
| ncb.NSData.dataWithBytes_length_(linkedLibs, buffer.cast(), l.length); |
| return Data._(data); |
| } finally { |
| calloc.free(buffer); |
| } |
| } |
| |
| /// A new [Data] object containing the given bytes. |
| @Deprecated('Use Data.fromList instead') |
| factory Data.fromUint8List(Uint8List l) = Data.fromList; |
| |
| /// The number of bytes contained in the object. |
| /// |
| /// See [NSData.length](https://developer.apple.com/documentation/foundation/nsdata/1416769-length) |
| int get length => _nsObject.length; |
| |
| /// The data contained in the object. |
| /// |
| /// See [NSData.bytes](https://developer.apple.com/documentation/foundation/nsdata/1410616-bytes) |
| Uint8List get bytes { |
| final bytes = _nsObject.bytes; |
| if (bytes.address == 0) { |
| return Uint8List(0); |
| } else { |
| // `NSData.byte` has the same lifetime as the `NSData` so make a copy to |
| // ensure memory safety. |
| // TODO(https://github.com/dart-lang/ffigen/issues/375): Remove copy. |
| return Uint8List.fromList(bytes.cast<Uint8>().asTypedList(length)); |
| } |
| } |
| |
| @override |
| String toString() { |
| final subrange = |
| length == 0 ? Uint8List(0) : bytes.sublist(0, min(length - 1, 20)); |
| final b = subrange.map((e) => e.toRadixString(16)).join(); |
| return '[Data length=$length bytes=0x$b...]'; |
| } |
| } |
| |
| /// A container for byte data. |
| /// |
| /// See [NSMutableData](https://developer.apple.com/documentation/foundation/nsmutabledata) |
| class MutableData extends Data { |
| final ncb.NSMutableData _mutableData; |
| |
| MutableData._(ncb.NSMutableData super.c) |
| : _mutableData = c, |
| super._(); |
| |
| /// A new empty [MutableData]. |
| factory MutableData.empty() => |
| MutableData._(ncb.NSMutableData.dataWithCapacity_(linkedLibs, 0)); |
| |
| /// Appends the given data. |
| /// |
| /// See [NSMutableData appendBytes:length:](https://developer.apple.com/documentation/foundation/nsmutabledata/1407704-appendbytes) |
| void appendBytes(List<int> l) { |
| final f = calloc<Uint8>(l.length); |
| try { |
| f.asTypedList(l.length).setAll(0, l); |
| |
| _mutableData.appendBytes_length_(f.cast(), l.length); |
| } finally { |
| calloc.free(f); |
| } |
| } |
| |
| @override |
| String toString() { |
| final subrange = |
| length == 0 ? Uint8List(0) : bytes.sublist(0, min(length - 1, 20)); |
| final b = subrange.map((e) => e.toRadixString(16)).join(); |
| return '[MutableData length=$length bytes=0x$b...]'; |
| } |
| } |
| |
| /// The response associated with loading an URL. |
| /// |
| /// See [NSURLResponse](https://developer.apple.com/documentation/foundation/nsurlresponse) |
| class URLResponse extends _ObjectHolder<ncb.NSURLResponse> { |
| URLResponse._(super.c); |
| |
| factory URLResponse._exactURLResponseType(ncb.NSURLResponse response) { |
| if (ncb.NSHTTPURLResponse.isInstance(response)) { |
| return HTTPURLResponse._(ncb.NSHTTPURLResponse.castFrom(response)); |
| } |
| return URLResponse._(response); |
| } |
| |
| /// The expected amount of data returned with the response. |
| /// |
| /// See [NSURLResponse.expectedContentLength](https://developer.apple.com/documentation/foundation/nsurlresponse/1413507-expectedcontentlength) |
| int get expectedContentLength => _nsObject.expectedContentLength; |
| |
| /// The MIME type of the response. |
| /// |
| /// See [NSURLResponse.MIMEType](https://developer.apple.com/documentation/foundation/nsurlresponse/1411613-mimetype) |
| String? get mimeType => toStringOrNull(_nsObject.MIMEType); |
| |
| @override |
| String toString() => '[URLResponse ' |
| 'mimeType=$mimeType ' |
| 'expectedContentLength=$expectedContentLength' |
| ']'; |
| } |
| |
| /// The response associated with loading a HTTP URL. |
| /// |
| /// See [NSHTTPURLResponse](https://developer.apple.com/documentation/foundation/nshttpurlresponse) |
| class HTTPURLResponse extends URLResponse { |
| final ncb.NSHTTPURLResponse _httpUrlResponse; |
| |
| HTTPURLResponse._(ncb.NSHTTPURLResponse super.c) |
| : _httpUrlResponse = c, |
| super._(); |
| |
| /// The HTTP status code of the response (e.g. 200). |
| /// |
| /// See [HTTPURLResponse.statusCode](https://developer.apple.com/documentation/foundation/nshttpurlresponse/1409395-statuscode) |
| int get statusCode => _httpUrlResponse.statusCode; |
| |
| /// The HTTP headers of the response. |
| /// |
| /// See [HTTPURLResponse.allHeaderFields](https://developer.apple.com/documentation/foundation/nshttpurlresponse/1417930-allheaderfields) |
| Map<String, String> get allHeaderFields { |
| final headers = |
| ncb.NSDictionary.castFrom(_httpUrlResponse.allHeaderFields!); |
| return stringDictToMap(headers); |
| } |
| |
| @override |
| String toString() => '[HTTPURLResponse ' |
| 'statusCode=$statusCode ' |
| 'mimeType=$mimeType ' |
| 'expectedContentLength=$expectedContentLength' |
| ']'; |
| } |
| |
| /// The possible states of a [URLSessionTask]. |
| /// |
| /// See [NSURLSessionTaskState](https://developer.apple.com/documentation/foundation/nsurlsessiontaskstate) |
| enum URLSessionTaskState { |
| urlSessionTaskStateRunning, |
| urlSessionTaskStateSuspended, |
| urlSessionTaskStateCanceling, |
| urlSessionTaskStateCompleted, |
| } |
| |
| /// A WebSocket message. |
| /// |
| /// See [NSURLSessionWebSocketMessage](https://developer.apple.com/documentation/foundation/nsurlsessionwebsocketmessage) |
| class URLSessionWebSocketMessage |
| extends _ObjectHolder<ncb.NSURLSessionWebSocketMessage> { |
| URLSessionWebSocketMessage._(super.nsObject); |
| |
| /// Create a WebSocket data message. |
| /// |
| /// See [NSURLSessionWebSocketMessage initWithData:](https://developer.apple.com/documentation/foundation/nsurlsessionwebsocketmessage/3181192-initwithdata) |
| factory URLSessionWebSocketMessage.fromData(Data d) => |
| URLSessionWebSocketMessage._( |
| ncb.NSURLSessionWebSocketMessage.alloc(linkedLibs) |
| .initWithData_(d._nsObject)); |
| |
| /// Create a WebSocket string message. |
| /// |
| /// See [NSURLSessionWebSocketMessage initWitString:](https://developer.apple.com/documentation/foundation/nsurlsessionwebsocketmessage/3181193-initwithstring) |
| factory URLSessionWebSocketMessage.fromString(String s) => |
| URLSessionWebSocketMessage._( |
| ncb.NSURLSessionWebSocketMessage.alloc(linkedLibs) |
| .initWithString_(s.toNSString(linkedLibs))); |
| |
| /// The data associated with the WebSocket message. |
| /// |
| /// Will be `null` if the [URLSessionWebSocketMessage] is a string message. |
| /// |
| /// See [NSURLSessionWebSocketMessage.data](https://developer.apple.com/documentation/foundation/nsurlsessionwebsocketmessage/3181191-data) |
| Data? get data => _nsObject.data == null ? null : Data._(_nsObject.data!); |
| |
| /// The string associated with the WebSocket message. |
| /// |
| /// Will be `null` if the [URLSessionWebSocketMessage] is a data message. |
| /// |
| /// See [NSURLSessionWebSocketMessage.string](https://developer.apple.com/documentation/foundation/nsurlsessionwebsocketmessage/3181194-string) |
| String? get string => toStringOrNull(_nsObject.string); |
| |
| /// The type of the WebSocket message. |
| /// |
| /// See [NSURLSessionWebSocketMessage.type](https://developer.apple.com/documentation/foundation/nsurlsessionwebsocketmessage/3181195-type) |
| URLSessionWebSocketMessageType get type => |
| URLSessionWebSocketMessageType.values[_nsObject.type]; |
| |
| @override |
| String toString() => |
| '[URLSessionWebSocketMessage type=$type string=$string data=$data]'; |
| } |
| |
| /// A task associated with downloading a URI. |
| /// |
| /// See [NSURLSessionTask](https://developer.apple.com/documentation/foundation/nsurlsessiontask) |
| class URLSessionTask extends _ObjectHolder<ncb.NSURLSessionTask> { |
| URLSessionTask._(super.c); |
| |
| /// Cancels the task. |
| /// |
| /// See [NSURLSessionTask cancel](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1411591-cancel) |
| void cancel() { |
| _nsObject.cancel(); |
| } |
| |
| /// Resumes a suspended task (new tasks start as suspended). |
| /// |
| /// See [NSURLSessionTask resume](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1411121-resume) |
| void resume() { |
| _nsObject.resume(); |
| } |
| |
| /// Suspends a task (prevents it from transferring data). |
| /// |
| /// See [NSURLSessionTask suspend](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1411565-suspend) |
| void suspend() { |
| _nsObject.suspend(); |
| } |
| |
| /// The current state of the task. |
| /// |
| /// See [NSURLSessionTask.state](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1409888-state) |
| URLSessionTaskState get state => URLSessionTaskState.values[_nsObject.state]; |
| |
| /// The relative priority [0, 1] that the host should use to handle the |
| /// request. |
| /// |
| /// See [NSURLSessionTask.priority](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1410569-priority) |
| double get priority => _nsObject.priority; |
| |
| /// The relative priority [0, 1] that the host should use to handle the |
| /// request. |
| /// |
| /// See [NSURLSessionTask.priority](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1410569-priority) |
| set priority(double value) => _nsObject.priority = value; |
| |
| /// The request currently being handled by the task. |
| /// |
| /// May be different from [originalRequest] if the server responds with a |
| /// redirect. |
| /// |
| /// See [NSURLSessionTask.currentRequest](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1411649-currentrequest) |
| URLRequest? get currentRequest { |
| final request = _nsObject.currentRequest; |
| if (request == null) { |
| return null; |
| } else { |
| return URLRequest._(request); |
| } |
| } |
| |
| /// The original request associated with the task. |
| /// |
| /// May be different from [currentRequest] if the server responds with a |
| /// redirect. |
| /// |
| /// See [NSURLSessionTask.originalRequest](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1411572-originalrequest) |
| URLRequest? get originalRequest { |
| final request = _nsObject.originalRequest; |
| if (request == null) { |
| return null; |
| } else { |
| return URLRequest._(request); |
| } |
| } |
| |
| /// The server response to the request associated with this task. |
| /// |
| /// See [NSURLSessionTask.response](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1410586-response) |
| URLResponse? get response { |
| final nsResponse = _nsObject.response; |
| if (nsResponse == null) { |
| return null; |
| } |
| return URLResponse._exactURLResponseType(nsResponse); |
| } |
| |
| /// An error indicating why the task failed or `null` on success. |
| /// |
| /// See [NSURLSessionTask.error](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1408145-error) |
| Error? get error { |
| final error = _nsObject.error; |
| if (error == null) { |
| return null; |
| } else { |
| return Error._(error); |
| } |
| } |
| |
| /// The user-assigned description for the task. |
| /// |
| /// See [NSURLSessionTask.taskDescription](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1409798-taskdescription) |
| String get taskDescription => _nsObject.taskDescription.toString(); |
| |
| /// The user-assigned description for the task. |
| /// |
| /// See [NSURLSessionTask.taskDescription](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1409798-taskdescription) |
| set taskDescription(String value) => |
| _nsObject.taskDescription = value.toNSString(linkedLibs); |
| |
| /// A unique ID for the [URLSessionTask] in a [URLSession]. |
| /// |
| /// See [NSURLSessionTask.taskIdentifier](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1411231-taskidentifier) |
| int get taskIdentifier => _nsObject.taskIdentifier; |
| |
| /// The number of content bytes that are expected to be received from the |
| /// server. |
| /// |
| /// See [NSURLSessionTask.countOfBytesReceived](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1410663-countofbytesexpectedtoreceive) |
| int get countOfBytesExpectedToReceive => |
| _nsObject.countOfBytesExpectedToReceive; |
| |
| /// The number of content bytes that have been received from the server. |
| /// |
| /// See [NSURLSessionTask.countOfBytesReceived](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1411581-countofbytesreceived) |
| int get countOfBytesReceived => _nsObject.countOfBytesReceived; |
| |
| /// The number of content bytes that the task expects to send to the server. |
| /// |
| /// See [NSURLSessionTask.countOfBytesExpectedToSend](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1411534-countofbytesexpectedtosend) |
| int get countOfBytesExpectedToSend => _nsObject.countOfBytesExpectedToSend; |
| |
| /// Whether the body of the response should be delivered incrementally or not. |
| /// |
| /// [NSURLSessionTask.countOfBytesSent](https://developer.apple.com/documentation/foundation/nsurlsessiontask/1410444-countofbytessent) |
| int get countOfBytesSent => _nsObject.countOfBytesSent; |
| |
| /// Whether the body of the response should be delivered incrementally or not. |
| /// |
| /// See [NSURLSessionTask.prefersIncrementalDelivery](https://developer.apple.com/documentation/foundation/nsurlsessiontask/3735881-prefersincrementaldelivery) |
| bool get prefersIncrementalDelivery => _nsObject.prefersIncrementalDelivery; |
| |
| /// Whether the body of the response should be delivered incrementally or not. |
| /// |
| /// See [NSURLSessionTask.prefersIncrementalDelivery](https://developer.apple.com/documentation/foundation/nsurlsessiontask/3735881-prefersincrementaldelivery) |
| set prefersIncrementalDelivery(bool value) => |
| _nsObject.prefersIncrementalDelivery = value; |
| |
| String _toStringHelper(String className) => '[$className ' |
| 'taskDescription=$taskDescription ' |
| 'taskIdentifier=$taskIdentifier ' |
| 'countOfBytesExpectedToReceive=$countOfBytesExpectedToReceive ' |
| 'countOfBytesReceived=$countOfBytesReceived ' |
| 'countOfBytesExpectedToSend=$countOfBytesExpectedToSend ' |
| 'countOfBytesSent=$countOfBytesSent ' |
| 'priority=$priority ' |
| 'state=$state ' |
| 'prefersIncrementalDelivery=$prefersIncrementalDelivery' |
| ']'; |
| |
| @override |
| String toString() => _toStringHelper('URLSessionTask'); |
| } |
| |
| /// A task associated with downloading a URI to a file. |
| /// |
| /// See [NSURLSessionDownloadTask](https://developer.apple.com/documentation/foundation/nsurlsessiondownloadtask) |
| class URLSessionDownloadTask extends URLSessionTask { |
| URLSessionDownloadTask._(ncb.NSURLSessionDownloadTask super.c) : super._(); |
| |
| @override |
| String toString() => _toStringHelper('URLSessionDownloadTask'); |
| } |
| |
| /// A task associated with a WebSocket connection. |
| /// |
| /// See [NSURLSessionWebSocketTask](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask) |
| class URLSessionWebSocketTask extends URLSessionTask { |
| final ncb.NSURLSessionWebSocketTask _urlSessionWebSocketTask; |
| |
| URLSessionWebSocketTask._(ncb.NSURLSessionWebSocketTask super.c) |
| : _urlSessionWebSocketTask = c, |
| super._(); |
| |
| /// The close code set when the WebSocket connection is closed. |
| /// |
| /// See [NSURLSessionWebSocketTask.closeCode](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask/3181201-closecode) |
| int get closeCode => _urlSessionWebSocketTask.closeCode; |
| |
| /// The close reason set when the WebSocket connection is closed. |
| /// If there is no close reason available this property will be null. |
| /// |
| /// See [NSURLSessionWebSocketTask.closeReason](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask/3181202-closereason) |
| Data? get closeReason { |
| final reason = _urlSessionWebSocketTask.closeReason; |
| if (reason == null) { |
| return null; |
| } else { |
| return Data._(reason); |
| } |
| } |
| |
| /// Sends a single WebSocket message. |
| /// |
| /// The returned future will complete successfully when the message is sent |
| /// and with an [Error] on failure. |
| /// |
| /// See [NSURLSessionWebSocketTask.sendMessage:completionHandler:](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask/3181205-sendmessage) |
| Future<void> sendMessage(URLSessionWebSocketMessage message) async { |
| final completer = Completer<void>(); |
| final completionPort = ReceivePort(); |
| completionPort.listen((message) { |
| final ep = Pointer<ncb.ObjCObject>.fromAddress(message as int); |
| if (ep.address == 0) { |
| completer.complete(); |
| } else { |
| final error = Error._(ncb.NSError.castFromPointer(linkedLibs, ep, |
| retain: false, release: true)); |
| completer.completeError(error); |
| } |
| completionPort.close(); |
| }); |
| |
| helperLibs.CUPHTTPSendMessage(_urlSessionWebSocketTask.pointer, |
| message._nsObject.pointer, completionPort.sendPort.nativePort); |
| await completer.future; |
| } |
| |
| /// Receives a single WebSocket message. |
| /// |
| /// Throws an [Error] on failure. |
| /// |
| /// See [NSURLSessionWebSocketTask.receiveMessageWithCompletionHandler:](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask/3181204-receivemessagewithcompletionhand) |
| Future<URLSessionWebSocketMessage> receiveMessage() async { |
| final completer = Completer<URLSessionWebSocketMessage>(); |
| final completionPort = ReceivePort(); |
| completionPort.listen((d) { |
| final messageAndError = d as List; |
| |
| final mp = Pointer<ncb.ObjCObject>.fromAddress(messageAndError[0] as int); |
| final ep = Pointer<ncb.ObjCObject>.fromAddress(messageAndError[1] as int); |
| |
| final message = mp.address == 0 |
| ? null |
| : URLSessionWebSocketMessage._( |
| ncb.NSURLSessionWebSocketMessage.castFromPointer(linkedLibs, mp, |
| retain: false, release: true)); |
| final error = ep.address == 0 |
| ? null |
| : Error._(ncb.NSError.castFromPointer(linkedLibs, ep, |
| retain: false, release: true)); |
| |
| if (error != null) { |
| completer.completeError(error); |
| } else { |
| completer.complete(message!); |
| } |
| completionPort.close(); |
| }); |
| |
| helperLibs.CUPHTTPReceiveMessage( |
| _urlSessionWebSocketTask.pointer, completionPort.sendPort.nativePort); |
| return completer.future; |
| } |
| |
| /// Sends close frame with the given code and optional reason. |
| /// |
| /// See [NSURLSessionWebSocketTask.cancelWithCloseCode:reason:](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask/3181200-cancelwithclosecode) |
| void cancelWithCloseCode(int closeCode, Data? reason) { |
| _urlSessionWebSocketTask.cancelWithCloseCode_reason_( |
| closeCode, reason?._nsObject); |
| } |
| |
| @override |
| String toString() => _toStringHelper('NSURLSessionWebSocketTask'); |
| } |
| |
| /// A request to load a URL. |
| /// |
| /// See [NSURLRequest](https://developer.apple.com/documentation/foundation/nsurlrequest) |
| class URLRequest extends _ObjectHolder<ncb.NSURLRequest> { |
| URLRequest._(super.c); |
| |
| /// Creates a request for a URL. |
| /// |
| /// See [NSURLRequest.requestWithURL:](https://developer.apple.com/documentation/foundation/nsurlrequest/1528603-requestwithurl) |
| factory URLRequest.fromUrl(Uri uri) => URLRequest._( |
| ncb.NSURLRequest.requestWithURL_(linkedLibs, uriToNSURL(uri))); |
| |
| /// Returns all of the HTTP headers for the request. |
| /// |
| /// See [NSURLRequest.allHTTPHeaderFields](https://developer.apple.com/documentation/foundation/nsurlrequest/1418477-allhttpheaderfields) |
| Map<String, String>? get allHttpHeaderFields { |
| if (_nsObject.allHTTPHeaderFields == null) { |
| return null; |
| } else { |
| final headers = ncb.NSDictionary.castFrom(_nsObject.allHTTPHeaderFields!); |
| return stringDictToMap(headers); |
| } |
| } |
| |
| /// Controls how to deal with caching for the request. |
| /// |
| /// See [NSURLSession.cachePolicy](https://developer.apple.com/documentation/foundation/nsurlrequest/1407944-cachepolicy) |
| URLRequestCachePolicy get cachePolicy => |
| URLRequestCachePolicy.values[_nsObject.cachePolicy]; |
| |
| /// The body of the request. |
| /// |
| /// See [NSURLRequest.HTTPBody](https://developer.apple.com/documentation/foundation/nsurlrequest/1411317-httpbody) |
| Data? get httpBody { |
| final body = _nsObject.HTTPBody; |
| if (body == null) { |
| return null; |
| } |
| return Data._(ncb.NSData.castFrom(body)); |
| } |
| |
| /// The HTTP request method (e.g. 'GET'). |
| /// |
| /// See [NSURLRequest.HTTPMethod](https://developer.apple.com/documentation/foundation/nsurlrequest/1413030-httpmethod) |
| /// |
| /// NOTE: The documentation for `NSURLRequest.HTTPMethod` says that the |
| /// property is nullable but, in practice, assigning it to null will produce |
| /// an error. |
| String get httpMethod => _nsObject.HTTPMethod!.toString(); |
| |
| /// The timeout interval during the connection attempt. |
| /// |
| /// See [NSURLSession.timeoutInterval](https://developer.apple.com/documentation/foundation/nsurlrequest/1418229-timeoutinterval) |
| Duration get timeoutInterval => Duration( |
| microseconds: |
| (_nsObject.timeoutInterval * Duration.microsecondsPerSecond).round()); |
| |
| /// The requested URL. |
| /// |
| /// See [URLRequest.URL](https://developer.apple.com/documentation/foundation/nsurlrequest/1408996-url) |
| Uri? get url { |
| final nsUrl = _nsObject.URL; |
| if (nsUrl == null) { |
| return null; |
| } |
| return Uri.parse(nsUrl.absoluteString!.toString()); |
| } |
| |
| @override |
| String toString() => '[URLRequest ' |
| 'allHttpHeaderFields=$allHttpHeaderFields ' |
| 'cachePolicy=$cachePolicy ' |
| 'httpBody=$httpBody ' |
| 'httpMethod=$httpMethod ' |
| 'timeoutInterval=$timeoutInterval ' |
| 'url=$url ' |
| ']'; |
| } |
| |
| /// A mutable request to load a URL. |
| /// |
| /// See [NSMutableURLRequest](https://developer.apple.com/documentation/foundation/nsmutableurlrequest) |
| class MutableURLRequest extends URLRequest { |
| final ncb.NSMutableURLRequest _mutableUrlRequest; |
| |
| MutableURLRequest._(ncb.NSMutableURLRequest super.c) |
| : _mutableUrlRequest = c, |
| super._(); |
| |
| /// Creates a request for a URL. |
| /// |
| /// See [NSMutableURLRequest.requestWithURL:](https://developer.apple.com/documentation/foundation/nsmutableurlrequest/1414617-allhttpheaderfields) |
| factory MutableURLRequest.fromUrl(Uri uri) { |
| final url = ncb.NSURL |
| .URLWithString_(linkedLibs, uri.toString().toNSString(linkedLibs)); |
| return MutableURLRequest._( |
| ncb.NSMutableURLRequest.requestWithURL_(linkedLibs, url)); |
| } |
| |
| set cachePolicy(URLRequestCachePolicy value) => |
| _mutableUrlRequest.cachePolicy = value.index; |
| |
| set httpBody(Data? data) { |
| _mutableUrlRequest.HTTPBody = data?._nsObject; |
| } |
| |
| /// Sets the body of the request to the given [Stream]. |
| /// |
| /// See [NSMutableURLRequest.HTTPBodyStream](https://developer.apple.com/documentation/foundation/nsurlrequest/1407341-httpbodystream). |
| set httpBodyStream(Stream<List<int>> stream) { |
| _mutableUrlRequest.HTTPBodyStream = _streamToNSInputStream(stream); |
| } |
| |
| set httpMethod(String method) { |
| _mutableUrlRequest.HTTPMethod = method.toNSString(linkedLibs); |
| } |
| |
| set timeoutInterval(Duration interval) { |
| _mutableUrlRequest.timeoutInterval = |
| interval.inMicroseconds.toDouble() / Duration.microsecondsPerSecond; |
| } |
| |
| /// Set the value of a header field. |
| /// |
| /// See [NSMutableURLRequest setValue:forHTTPHeaderField:](https://developer.apple.com/documentation/foundation/nsmutableurlrequest/1408793-setvalue) |
| void setValueForHttpHeaderField(String value, String field) { |
| _mutableUrlRequest.setValue_forHTTPHeaderField_( |
| field.toNSString(linkedLibs), value.toNSString(linkedLibs)); |
| } |
| |
| @override |
| String toString() => '[MutableURLRequest ' |
| 'allHttpHeaderFields=$allHttpHeaderFields ' |
| 'cachePolicy=$cachePolicy ' |
| 'httpBody=$httpBody ' |
| 'httpMethod=$httpMethod ' |
| 'timeoutInterval=$timeoutInterval ' |
| 'url=$url ' |
| ']'; |
| } |
| |
| /// Setup delegation for the given [task] to the given methods. |
| /// |
| /// Specifically, this causes the Objective-C-implemented delegate installed |
| /// with |
| /// [sessionWithConfiguration:delegate:delegateQueue:](https://developer.apple.com/documentation/foundation/nsurlsession/1411597-sessionwithconfiguration) |
| /// to send a [ncb.CUPHTTPForwardedDelegate] object to a send port, which is |
| /// then processed by [_setupDelegation] and forwarded to the given methods. |
| void _setupDelegation( |
| ncb.CUPHTTPClientDelegate delegate, |
| URLSession session, |
| URLSessionTask task, { |
| URLRequest? Function(URLSession session, URLSessionTask task, |
| HTTPURLResponse response, URLRequest newRequest)? |
| onRedirect, |
| URLSessionResponseDisposition Function( |
| URLSession session, URLSessionTask task, URLResponse response)? |
| onResponse, |
| void Function(URLSession session, URLSessionTask task, Data error)? onData, |
| void Function(URLSession session, URLSessionDownloadTask task, Uri uri)? |
| onFinishedDownloading, |
| void Function(URLSession session, URLSessionTask task, Error? error)? |
| onComplete, |
| void Function( |
| URLSession session, URLSessionWebSocketTask task, String? protocol)? |
| onWebSocketTaskOpened, |
| void Function(URLSession session, URLSessionWebSocketTask task, int closeCode, |
| Data? reason)? |
| onWebSocketTaskClosed, |
| }) { |
| final responsePort = ReceivePort(); |
| responsePort.listen((d) { |
| final message = d as List; |
| final messageType = message[0]; |
| final dp = Pointer<ncb.ObjCObject>.fromAddress(message[1] as int); |
| |
| final forwardedDelegate = ncb.CUPHTTPForwardedDelegate.castFromPointer( |
| helperLibs, dp, |
| retain: true, release: true); |
| |
| switch (messageType) { |
| case ncb.MessageType.RedirectMessage: |
| final forwardedRedirect = |
| ncb.CUPHTTPForwardedRedirect.castFrom(forwardedDelegate); |
| URLRequest? redirectRequest; |
| |
| try { |
| final request = URLRequest._( |
| ncb.NSURLRequest.castFrom(forwardedRedirect.request!)); |
| |
| if (onRedirect == null) { |
| redirectRequest = request; |
| break; |
| } |
| try { |
| final response = HTTPURLResponse._( |
| ncb.NSHTTPURLResponse.castFrom(forwardedRedirect.response!)); |
| redirectRequest = onRedirect(session, task, response, request); |
| } catch (e) { |
| // TODO(https://github.com/dart-lang/ffigen/issues/386): Package |
| // this exception as an `Error` and call the completion function |
| // with it. |
| } |
| } finally { |
| forwardedRedirect.finishWithRequest_(redirectRequest?._nsObject); |
| } |
| break; |
| case ncb.MessageType.ResponseMessage: |
| final forwardedResponse = |
| ncb.CUPHTTPForwardedResponse.castFrom(forwardedDelegate); |
| var disposition = |
| URLSessionResponseDisposition.urlSessionResponseCancel; |
| |
| try { |
| if (onResponse == null) { |
| disposition = URLSessionResponseDisposition.urlSessionResponseAllow; |
| break; |
| } |
| final response = |
| URLResponse._exactURLResponseType(forwardedResponse.response!); |
| |
| try { |
| disposition = onResponse(session, task, response); |
| } catch (e) { |
| // TODO(https://github.com/dart-lang/ffigen/issues/386): Package |
| // this exception as an `Error` and call the completion function |
| // with it. |
| } |
| } finally { |
| forwardedResponse.finishWithDisposition_(disposition.index); |
| } |
| break; |
| case ncb.MessageType.DataMessage: |
| final forwardedData = |
| ncb.CUPHTTPForwardedData.castFrom(forwardedDelegate); |
| |
| try { |
| if (onData == null) { |
| break; |
| } |
| try { |
| onData(session, task, |
| Data._(ncb.NSData.castFrom(forwardedData.data!))); |
| } catch (e) { |
| // TODO(https://github.com/dart-lang/ffigen/issues/386): Package |
| // this exception as an `Error` and call the completion function |
| // with it. |
| } |
| } finally { |
| forwardedData.finish(); |
| } |
| break; |
| case ncb.MessageType.FinishedDownloading: |
| final finishedDownloading = |
| ncb.CUPHTTPForwardedFinishedDownloading.castFrom(forwardedDelegate); |
| try { |
| if (onFinishedDownloading == null) { |
| break; |
| } |
| try { |
| onFinishedDownloading( |
| session, |
| task as URLSessionDownloadTask, |
| Uri.parse( |
| finishedDownloading.location!.absoluteString!.toString())); |
| } catch (e) { |
| // TODO(https://github.com/dart-lang/ffigen/issues/386): Package |
| // this exception as an `Error` and call the completion function |
| // with it. |
| } |
| } finally { |
| finishedDownloading.finish(); |
| } |
| break; |
| case ncb.MessageType.CompletedMessage: |
| final forwardedComplete = |
| ncb.CUPHTTPForwardedComplete.castFrom(forwardedDelegate); |
| |
| try { |
| if (onComplete == null) { |
| break; |
| } |
| Error? error; |
| if (forwardedComplete.error != null) { |
| error = Error._(ncb.NSError.castFrom(forwardedComplete.error!)); |
| } |
| try { |
| onComplete(session, task, error); |
| } catch (e) { |
| // TODO(https://github.com/dart-lang/ffigen/issues/386): Package |
| // this exception as an `Error` and call the completion function |
| // with it. |
| } |
| } finally { |
| forwardedComplete.finish(); |
| responsePort.close(); |
| } |
| break; |
| case ncb.MessageType.WebSocketOpened: |
| final webSocketOpened = |
| ncb.CUPHTTPForwardedWebSocketOpened.castFrom(forwardedDelegate); |
| |
| try { |
| if (onWebSocketTaskOpened == null) { |
| break; |
| } |
| try { |
| onWebSocketTaskOpened(session, task as URLSessionWebSocketTask, |
| webSocketOpened.protocol?.toString()); |
| } catch (e) { |
| // TODO(https://github.com/dart-lang/ffigen/issues/386): Package |
| // this exception as an `Error` and call the completion function |
| // with it. |
| } |
| } finally { |
| webSocketOpened.finish(); |
| } |
| break; |
| case ncb.MessageType.WebSocketClosed: |
| final webSocketClosed = |
| ncb.CUPHTTPForwardedWebSocketClosed.castFrom(forwardedDelegate); |
| |
| try { |
| if (onWebSocketTaskClosed == null) { |
| break; |
| } |
| try { |
| onWebSocketTaskClosed( |
| session, |
| task as URLSessionWebSocketTask, |
| webSocketClosed.closeCode, |
| webSocketClosed.reason == null |
| ? null |
| : Data._(webSocketClosed.reason!)); |
| } catch (e) { |
| // TODO(https://github.com/dart-lang/ffigen/issues/386): Package |
| // this exception as an `Error` and call the completion function |
| // with it. |
| } |
| } finally { |
| webSocketClosed.finish(); |
| } |
| break; |
| } |
| }); |
| final config = ncb.CUPHTTPTaskConfiguration.castFrom( |
| ncb.CUPHTTPTaskConfiguration.alloc(helperLibs) |
| .initWithPort_(responsePort.sendPort.nativePort)); |
| |
| delegate.registerTask_withConfiguration_(task._nsObject, config); |
| } |
| |
| /// A client that can make network requests to a server. |
| /// |
| /// See [NSURLSession](https://developer.apple.com/documentation/foundation/nsurlsession) |
| class URLSession extends _ObjectHolder<ncb.NSURLSession> { |
| // Provide our own native delegate to `NSURLSession` because delegates can be |
| // called on arbitrary threads and Dart code cannot be. |
| static final _delegate = ncb.CUPHTTPClientDelegate.new1(helperLibs); |
| // Indicates if the session is a background session. Copied from the |
| // [URLSessionConfiguration._isBackground] associated with this [URLSession]. |
| final bool _isBackground; |
| |
| final URLRequest? Function(URLSession session, URLSessionTask task, |
| HTTPURLResponse response, URLRequest newRequest)? _onRedirect; |
| final URLSessionResponseDisposition Function( |
| URLSession session, URLSessionTask task, URLResponse response)? |
| _onResponse; |
| final void Function(URLSession session, URLSessionTask task, Data error)? |
| _onData; |
| final void Function(URLSession session, URLSessionTask task, Error? error)? |
| _onComplete; |
| final void Function(URLSession session, URLSessionDownloadTask task, Uri uri)? |
| _onFinishedDownloading; |
| final void Function( |
| URLSession session, URLSessionWebSocketTask task, String? protocol)? |
| _onWebSocketTaskOpened; |
| final void Function(URLSession session, URLSessionWebSocketTask task, |
| int closeCode, Data? reason)? _onWebSocketTaskClosed; |
| |
| URLSession._( |
| super.c, { |
| required bool isBackground, |
| URLRequest? Function(URLSession session, URLSessionTask task, |
| HTTPURLResponse response, URLRequest newRequest)? |
| onRedirect, |
| URLSessionResponseDisposition Function( |
| URLSession session, URLSessionTask task, URLResponse response)? |
| onResponse, |
| void Function(URLSession session, URLSessionTask task, Data error)? onData, |
| void Function(URLSession session, URLSessionDownloadTask task, Uri uri)? |
| onFinishedDownloading, |
| void Function(URLSession session, URLSessionTask task, Error? error)? |
| onComplete, |
| void Function( |
| URLSession session, URLSessionWebSocketTask task, String? protocol)? |
| onWebSocketTaskOpened, |
| void Function(URLSession session, URLSessionWebSocketTask task, |
| int closeCode, Data? reason)? |
| onWebSocketTaskClosed, |
| }) : _isBackground = isBackground, |
| _onRedirect = onRedirect, |
| _onResponse = onResponse, |
| _onData = onData, |
| _onFinishedDownloading = onFinishedDownloading, |
| _onComplete = onComplete, |
| _onWebSocketTaskOpened = onWebSocketTaskOpened, |
| _onWebSocketTaskClosed = onWebSocketTaskClosed; |
| |
| /// A client with reasonable default behavior. |
| /// |
| /// See [NSURLSession.sharedSession](https://developer.apple.com/documentation/foundation/nsurlsession/1409000-sharedsession) |
| factory URLSession.sharedSession() => URLSession.sessionWithConfiguration( |
| URLSessionConfiguration.defaultSessionConfiguration()); |
| |
| /// A client with a given configuration. |
| /// |
| /// If [onRedirect] is set then it will be called whenever a HTTP |
| /// request returns a redirect response (e.g. 302). The `response` parameter |
| /// contains the response from the server. The `newRequest` parameter contains |
| /// a follow-up request that would honor the server's redirect. If the return |
| /// value of this function is `null` then the redirect will not occur. |
| /// Otherwise, the returned [URLRequest] (usually `newRequest`) will be |
| /// executed. [onRedirect] will not be called for background sessions, which |
| /// automatically follow redirects. See |
| /// [URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:](https://developer.apple.com/documentation/foundation/nsurlsessiontaskdelegate/1411626-urlsession) |
| /// |
| /// If [onResponse] is set then it will be called whenever a valid response |
| /// is received. The returned [URLSessionResponseDisposition] will decide |
| /// how the content of the response is processed. See |
| /// [URLSession:dataTask:didReceiveResponse:completionHandler:](https://developer.apple.com/documentation/foundation/nsurlsessiondatadelegate/1410027-urlsession) |
| /// |
| /// If [onData] is set then it will be called whenever response data is |
| /// received. If the amount of received data is large, then it may be |
| /// called more than once. See |
| /// [URLSession:dataTask:didReceiveData:](https://developer.apple.com/documentation/foundation/nsurlsessiondatadelegate/1411528-urlsession) |
| /// |
| /// If [onFinishedDownloading] is set then it will be called whenever a |
| /// [URLSessionDownloadTask] has finished downloading. |
| /// |
| /// If [onComplete] is set then it will be called when a task completes. If |
| /// `error` is `null` then the request completed successfully. See |
| /// [URLSession:task:didCompleteWithError:](https://developer.apple.com/documentation/foundation/nsurlsessiontaskdelegate/1411610-urlsession) |
| /// |
| /// See [sessionWithConfiguration:delegate:delegateQueue:](https://developer.apple.com/documentation/foundation/nsurlsession/1411597-sessionwithconfiguration) |
| /// |
| /// If [onWebSocketTaskOpened] is set then it will be called when a |
| /// [URLSessionWebSocketTask] successfully negotiated the handshake with the |
| /// server. |
| /// |
| /// If [onWebSocketTaskClosed] is set then it will be called if a |
| /// [URLSessionWebSocketTask] receives a close control frame from the server. |
| /// NOTE: A [URLSessionWebSocketTask.receiveMessage] must be in flight for |
| /// [onWebSocketTaskClosed] to be called. |
| factory URLSession.sessionWithConfiguration( |
| URLSessionConfiguration config, { |
| URLRequest? Function(URLSession session, URLSessionTask task, |
| HTTPURLResponse response, URLRequest newRequest)? |
| onRedirect, |
| URLSessionResponseDisposition Function( |
| URLSession session, URLSessionTask task, URLResponse response)? |
| onResponse, |
| void Function(URLSession session, URLSessionTask task, Data error)? onData, |
| void Function(URLSession session, URLSessionDownloadTask task, Uri uri)? |
| onFinishedDownloading, |
| void Function(URLSession session, URLSessionTask task, Error? error)? |
| onComplete, |
| void Function( |
| URLSession session, URLSessionWebSocketTask task, String? protocol)? |
| onWebSocketTaskOpened, |
| void Function(URLSession session, URLSessionWebSocketTask task, |
| int? closeCode, Data? reason)? |
| onWebSocketTaskClosed, |
| }) { |
| // Avoid the complexity of simultaneous or out-of-order delegate callbacks |
| // by only allowing callbacks to execute sequentially. |
| // See https://developer.apple.com/forums/thread/47252 |
| // NOTE: this is likely to reduce throughput when there are multiple |
| // requests in flight because each call to a delegate waits on a lock |
| // that is unlocked by Dart code. |
| final queue = ncb.NSOperationQueue.new1(linkedLibs) |
| ..maxConcurrentOperationCount = 1 |
| ..name = |
| 'cupertino_http.NSURLSessionDelegateQueue'.toNSString(linkedLibs); |
| |
| return URLSession._( |
| ncb.NSURLSession.sessionWithConfiguration_delegate_delegateQueue_( |
| linkedLibs, config._nsObject, _delegate, queue), |
| isBackground: config._isBackground, |
| onRedirect: onRedirect, |
| onResponse: onResponse, |
| onData: onData, |
| onFinishedDownloading: onFinishedDownloading, |
| onComplete: onComplete, |
| onWebSocketTaskOpened: onWebSocketTaskOpened, |
| onWebSocketTaskClosed: onWebSocketTaskClosed); |
| } |
| |
| /// A **copy** of the configuration for this session. |
| /// |
| /// See [NSURLSession.configuration](https://developer.apple.com/documentation/foundation/nsurlsession/1411477-configuration) |
| URLSessionConfiguration get configuration => URLSessionConfiguration._( |
| ncb.NSURLSessionConfiguration.castFrom(_nsObject.configuration!), |
| isBackground: _isBackground); |
| |
| /// A description of the session that may be useful for debugging. |
| /// |
| /// See [NSURLSession.sessionDescription](https://developer.apple.com/documentation/foundation/nsurlsession/1408277-sessiondescription) |
| String? get sessionDescription => |
| toStringOrNull(_nsObject.sessionDescription); |
| set sessionDescription(String? value) => |
| _nsObject.sessionDescription = value?.toNSString(linkedLibs); |
| |
| /// Create a [URLSessionTask] that accesses a server URL. |
| /// |
| /// See [NSURLSession dataTaskWithRequest:](https://developer.apple.com/documentation/foundation/nsurlsession/1410592-datataskwithrequest) |
| URLSessionTask dataTaskWithRequest(URLRequest request) { |
| final task = |
| URLSessionTask._(_nsObject.dataTaskWithRequest_(request._nsObject)); |
| _setupDelegation(_delegate, this, task, |
| onComplete: _onComplete, |
| onData: _onData, |
| onFinishedDownloading: _onFinishedDownloading, |
| onRedirect: _onRedirect, |
| onResponse: _onResponse); |
| return task; |
| } |
| |
| /// Creates a [URLSessionTask] that accesses a server URL and calls |
| /// [completion] when done. |
| /// |
| /// See [NSURLSession dataTaskWithRequest:completionHandler:](https://developer.apple.com/documentation/foundation/nsurlsession/1407613-datataskwithrequest) |
| URLSessionTask dataTaskWithCompletionHandler( |
| URLRequest request, |
| void Function(Data? data, HTTPURLResponse? response, Error? error) |
| completion) { |
| // This method cannot be implemented by simply calling |
| // `dataTaskWithRequest:completionHandler:` because the completion handler |
| // will invoke the Dart callback on an arbitrary thread and Dart code |
| // cannot be run that way |
| // (see https://github.com/dart-lang/sdk/issues/37022). |
| // |
| // Instead, we use `dataTaskWithRequest:` and: |
| // 1. create a port to receive information about the request. |
| // 2. use a delegate to send information about the task to the port |
| // 3. call the user-provided completion function when we receive the |
| // `CompletedMessage` message type. |
| final task = |
| URLSessionTask._(_nsObject.dataTaskWithRequest_(request._nsObject)); |
| |
| HTTPURLResponse? finalResponse; |
| MutableData? allData; |
| |
| _setupDelegation(_delegate, this, task, onRedirect: _onRedirect, onResponse: |
| (URLSession session, URLSessionTask task, URLResponse response) { |
| finalResponse = |
| HTTPURLResponse._(ncb.NSHTTPURLResponse.castFrom(response._nsObject)); |
| return URLSessionResponseDisposition.urlSessionResponseAllow; |
| }, onData: (URLSession session, URLSessionTask task, Data data) { |
| allData ??= MutableData.empty(); |
| allData!.appendBytes(data.bytes); |
| }, onComplete: (URLSession session, URLSessionTask task, Error? error) { |
| completion(allData == null ? null : Data.fromData(allData!), |
| finalResponse, error); |
| }); |
| |
| return URLSessionTask._(task._nsObject); |
| } |
| |
| /// Creates a [URLSessionDownloadTask] that downloads the data from a server |
| /// URL. |
| /// |
| /// Provide a `onFinishedDownloading` handler in the [URLSession] factory to |
| /// receive notifications when the data has completed downloaded. |
| /// |
| /// See [NSURLSession downloadTaskWithRequest:](https://developer.apple.com/documentation/foundation/nsurlsession/1411481-downloadtaskwithrequest) |
| URLSessionDownloadTask downloadTaskWithRequest(URLRequest request) { |
| final task = URLSessionDownloadTask._( |
| _nsObject.downloadTaskWithRequest_(request._nsObject)); |
| _setupDelegation(_delegate, this, task, |
| onComplete: _onComplete, |
| onData: _onData, |
| onFinishedDownloading: _onFinishedDownloading, |
| onRedirect: _onRedirect, |
| onResponse: _onResponse); |
| return task; |
| } |
| |
| /// Creates a [URLSessionWebSocketTask] that represents a connection to a |
| /// WebSocket endpoint. |
| /// |
| /// To add custom protocols, add a "Sec-WebSocket-Protocol" header with a list |
| /// of protocols to [request]. |
| /// |
| /// See [NSURLSession webSocketTaskWithRequest:](https://developer.apple.com/documentation/foundation/nsurlsession/3235750-websockettaskwithrequest) |
| URLSessionWebSocketTask webSocketTaskWithRequest(URLRequest request) { |
| if (_isBackground) { |
| throw UnsupportedError( |
| 'WebSocket tasks are not supported in background sessions'); |
| } |
| final task = URLSessionWebSocketTask._( |
| _nsObject.webSocketTaskWithRequest_(request._nsObject)); |
| _setupDelegation(_delegate, this, task, |
| onComplete: _onComplete, |
| onData: _onData, |
| onFinishedDownloading: _onFinishedDownloading, |
| onRedirect: _onRedirect, |
| onResponse: _onResponse, |
| onWebSocketTaskOpened: _onWebSocketTaskOpened, |
| onWebSocketTaskClosed: _onWebSocketTaskClosed); |
| return task; |
| } |
| } |