diff --git a/pkgs/cupertino_http/example/integration_test/url_session_delegate_test.dart b/pkgs/cupertino_http/example/integration_test/url_session_delegate_test.dart index 50cf1245d3..0d86694c09 100644 --- a/pkgs/cupertino_http/example/integration_test/url_session_delegate_test.dart +++ b/pkgs/cupertino_http/example/integration_test/url_session_delegate_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:cupertino_http/cupertino_http.dart'; @@ -435,6 +436,235 @@ void testOnRedirect(URLSessionConfiguration config) { }); } +void testOnWebSocketTaskOpened(URLSessionConfiguration config) { + group('onWebSocketTaskOpened', () { + late HttpServer server; + + setUp(() async { + server = (await HttpServer.bind('localhost', 0)) + ..listen((request) async { + if (request.requestedUri.queryParameters.containsKey('error')) { + request.response.statusCode = 500; + unawaited(request.response.close()); + return; + } + final webSocket = await WebSocketTransformer.upgrade( + request, + protocolSelector: (l) => 'myprotocol', + ); + await webSocket.close(); + }); + }); + tearDown(() { + server.close(); + }); + + test('with protocol', () async { + final c = Completer(); + late String? actualProtocol; + late URLSession actualSession; + late URLSessionWebSocketTask actualTask; + + final session = URLSession.sessionWithConfiguration(config, + onWebSocketTaskOpened: (s, t, p) { + actualSession = s; + actualTask = t; + actualProtocol = p; + c.complete(); + }); + + final request = MutableURLRequest.fromUrl( + Uri.parse('http://localhost:${server.port}')) + ..setValueForHttpHeaderField('Sec-WebSocket-Protocol', 'myprotocol'); + + final task = session.webSocketTaskWithRequest(request)..resume(); + await c.future; + expect(actualSession, session); + expect(actualTask, task); + expect(actualProtocol, 'myprotocol'); + }); + + test('without protocol', () async { + final c = Completer(); + late String? actualProtocol; + late URLSession actualSession; + late URLSessionWebSocketTask actualTask; + + final session = URLSession.sessionWithConfiguration(config, + onWebSocketTaskOpened: (s, t, p) { + actualSession = s; + actualTask = t; + actualProtocol = p; + c.complete(); + }); + + final request = MutableURLRequest.fromUrl( + Uri.parse('http://localhost:${server.port}')); + final task = session.webSocketTaskWithRequest(request)..resume(); + await c.future; + expect(actualSession, session); + expect(actualTask, task); + expect(actualProtocol, null); + }); + + test('server failure', () async { + final c = Completer(); + var onWebSocketTaskOpenedCalled = false; + + final session = URLSession.sessionWithConfiguration(config, + onWebSocketTaskOpened: (s, t, p) { + onWebSocketTaskOpenedCalled = true; + }, onComplete: (s, t, e) { + expect(e, isNotNull); + c.complete(); + }); + + final request = MutableURLRequest.fromUrl( + Uri.parse('http://localhost:${server.port}?error=1')); + session.webSocketTaskWithRequest(request).resume(); + await c.future; + expect(onWebSocketTaskOpenedCalled, false); + }); + }); +} + +void testOnWebSocketTaskClosed(URLSessionConfiguration config) { + group('testOnWebSocketTaskClosed', () { + late HttpServer server; + late int? serverCode; + late String? serverReason; + + setUp(() async { + server = (await HttpServer.bind('localhost', 0)) + ..listen((request) async { + if (request.requestedUri.queryParameters.containsKey('error')) { + request.response.statusCode = 500; + unawaited(request.response.close()); + return; + } + final webSocket = await WebSocketTransformer.upgrade( + request, + ); + await webSocket.close(serverCode, serverReason); + }); + }); + tearDown(() { + server.close(); + }); + + test('close no code', () async { + final c = Completer(); + late int actualCloseCode; + late String? actualReason; + late URLSession actualSession; + late URLSessionWebSocketTask actualTask; + + serverCode = null; + serverReason = null; + + final session = URLSession.sessionWithConfiguration(config, + onWebSocketTaskOpened: (session, task, protocol) {}, + onWebSocketTaskClosed: (session, task, closeCode, reason) { + actualSession = session; + actualTask = task; + actualCloseCode = closeCode!; + actualReason = utf8.decode(reason!.bytes); + c.complete(); + }); + + final request = MutableURLRequest.fromUrl( + Uri.parse('http://localhost:${server.port}')); + + final task = session.webSocketTaskWithRequest(request)..resume(); + + expect( + task.receiveMessage(), + throwsA(isA() + .having((e) => e.code, 'code', 57 // Socket is not connected. + ))); + await c.future; + expect(actualSession, session); + expect(actualTask, task); + expect(actualCloseCode, 1005); + expect(actualReason, ''); + }); + + test('close code', () async { + final c = Completer(); + late int actualCloseCode; + late String? actualReason; + late URLSession actualSession; + late URLSessionWebSocketTask actualTask; + + serverCode = 4000; + serverReason = null; + + final session = URLSession.sessionWithConfiguration(config, + onWebSocketTaskOpened: (session, task, protocol) {}, + onWebSocketTaskClosed: (session, task, closeCode, reason) { + actualSession = session; + actualTask = task; + actualCloseCode = closeCode!; + actualReason = utf8.decode(reason!.bytes); + c.complete(); + }); + + final request = MutableURLRequest.fromUrl( + Uri.parse('http://localhost:${server.port}')); + + final task = session.webSocketTaskWithRequest(request)..resume(); + + expect( + task.receiveMessage(), + throwsA(isA() + .having((e) => e.code, 'code', 57 // Socket is not connected. + ))); + await c.future; + expect(actualSession, session); + expect(actualTask, task); + expect(actualCloseCode, serverCode); + expect(actualReason, ''); + }); + + test('close code and reason', () async { + final c = Completer(); + late int actualCloseCode; + late String? actualReason; + late URLSession actualSession; + late URLSessionWebSocketTask actualTask; + + serverCode = 4000; + serverReason = 'no real reason'; + + final session = URLSession.sessionWithConfiguration(config, + onWebSocketTaskOpened: (session, task, protocol) {}, + onWebSocketTaskClosed: (session, task, closeCode, reason) { + actualSession = session; + actualTask = task; + actualCloseCode = closeCode!; + actualReason = utf8.decode(reason!.bytes); + c.complete(); + }); + + final request = MutableURLRequest.fromUrl( + Uri.parse('http://localhost:${server.port}')); + + final task = session.webSocketTaskWithRequest(request)..resume(); + + expect( + task.receiveMessage(), + throwsA(isA() + .having((e) => e.code, 'code', 57 // Socket is not connected. + ))); + await c.future; + expect(actualSession, session); + expect(actualTask, task); + expect(actualCloseCode, serverCode); + expect(actualReason, serverReason); + }); + }); +} + void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -446,6 +676,7 @@ void main() { testOnData(config); // onRedirect is not called for background sessions. testOnFinishedDownloading(config); + // WebSocket tasks are not supported in background sessions. }); group('defaultSessionConfiguration', () { @@ -455,6 +686,8 @@ void main() { testOnData(config); testOnRedirect(config); testOnFinishedDownloading(config); + testOnWebSocketTaskOpened(config); + testOnWebSocketTaskClosed(config); }); group('ephemeralSessionConfiguration', () { @@ -464,5 +697,7 @@ void main() { testOnData(config); testOnRedirect(config); testOnFinishedDownloading(config); + testOnWebSocketTaskOpened(config); + testOnWebSocketTaskClosed(config); }); } diff --git a/pkgs/cupertino_http/example/integration_test/url_session_task_test.dart b/pkgs/cupertino_http/example/integration_test/url_session_task_test.dart index 35e411b84a..c15f1a86d2 100644 --- a/pkgs/cupertino_http/example/integration_test/url_session_task_test.dart +++ b/pkgs/cupertino_http/example/integration_test/url_session_task_test.dart @@ -46,6 +46,15 @@ void testWebSocketTask() { await server.close(); }); + test('background session', () { + final session = URLSession.sessionWithConfiguration( + URLSessionConfiguration.backgroundSession('background')); + expect( + () => session.webSocketTaskWithRequest(URLRequest.fromUrl( + Uri.parse('ws://localhost:${server.port}/?noclose'))), + throwsUnsupportedError); + }); + test('client code and reason', () async { final session = URLSession.sharedSession(); final task = session.webSocketTaskWithRequest(URLRequest.fromUrl( diff --git a/pkgs/cupertino_http/lib/src/cupertino_api.dart b/pkgs/cupertino_http/lib/src/cupertino_api.dart index 1fa2524b3f..16c79e0f78 100644 --- a/pkgs/cupertino_http/lib/src/cupertino_api.dart +++ b/pkgs/cupertino_http/lib/src/cupertino_api.dart @@ -171,33 +171,44 @@ class Error extends _ObjectHolder implements Exception { /// See [NSURLSessionConfiguration](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration) class URLSessionConfiguration extends _ObjectHolder { - URLSessionConfiguration._(super.c); + // 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))); + 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)!)); + 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)!)); + URLSessionConfiguration._( + ncb.NSURLSessionConfiguration.castFrom( + ncb.NSURLSessionConfiguration.getEphemeralSessionConfiguration( + linkedLibs)!), + isBackground: false); /// Whether connections over a cellular network are allowed. /// @@ -970,18 +981,27 @@ class MutableURLRequest extends URLRequest { /// 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}) { + 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; @@ -1110,6 +1130,51 @@ void _setupDelegation( 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( @@ -1126,36 +1191,55 @@ class URLSession extends _ObjectHolder { // 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; - URLRequest? Function(URLSession session, URLSessionTask task, + final URLRequest? Function(URLSession session, URLSessionTask task, HTTPURLResponse response, URLRequest newRequest)? _onRedirect; - URLSessionResponseDisposition Function( + final URLSessionResponseDisposition Function( URLSession session, URLSessionTask task, URLResponse response)? _onResponse; - void Function(URLSession session, URLSessionTask task, Data error)? _onData; - void Function(URLSession session, URLSessionTask task, Error? error)? + final void Function(URLSession session, URLSessionTask task, Data error)? + _onData; + final void Function(URLSession session, URLSessionTask task, Error? error)? _onComplete; - void Function(URLSession session, URLSessionDownloadTask task, Uri uri)? + final void Function(URLSession session, URLSessionDownloadTask task, Uri uri)? _onFinishedDownloading; - - URLSession._(super.c, - {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}) - : _onRedirect = onRedirect, + 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; + _onComplete = onComplete, + _onWebSocketTaskOpened = onWebSocketTaskOpened, + _onWebSocketTaskClosed = onWebSocketTaskClosed; /// A client with reasonable default behavior. /// @@ -1193,19 +1277,35 @@ class URLSession extends _ObjectHolder { /// [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) - 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}) { + /// + /// 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 @@ -1220,18 +1320,22 @@ class URLSession extends _ObjectHolder { 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); + 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!)); + ncb.NSURLSessionConfiguration.castFrom(_nsObject.configuration!), + isBackground: _isBackground); /// A description of the session that may be useful for debugging. /// @@ -1324,6 +1428,10 @@ class URLSession extends _ObjectHolder { /// /// 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, @@ -1331,7 +1439,9 @@ class URLSession extends _ObjectHolder { onData: _onData, onFinishedDownloading: _onFinishedDownloading, onRedirect: _onRedirect, - onResponse: _onResponse); + onResponse: _onResponse, + onWebSocketTaskOpened: _onWebSocketTaskOpened, + onWebSocketTaskClosed: _onWebSocketTaskClosed); return task; } } diff --git a/pkgs/cupertino_http/lib/src/native_cupertino_bindings.dart b/pkgs/cupertino_http/lib/src/native_cupertino_bindings.dart index 273085f9e5..9ed49e403a 100644 --- a/pkgs/cupertino_http/lib/src/native_cupertino_bindings.dart +++ b/pkgs/cupertino_http/lib/src/native_cupertino_bindings.dart @@ -58684,6 +58684,82 @@ class NativeCupertinoHttp { ffi.Pointer)>(); late final _sel_location1 = _registerName1("location"); + late final _class_CUPHTTPForwardedWebSocketOpened1 = + _getClass1("CUPHTTPForwardedWebSocketOpened"); + late final _sel_initWithSession_webSocketTask_didOpenWithProtocol_1 = + _registerName1("initWithSession:webSocketTask:didOpenWithProtocol:"); + ffi.Pointer _objc_msgSend_491( + ffi.Pointer obj, + ffi.Pointer sel, + ffi.Pointer session, + ffi.Pointer webSocketTask, + ffi.Pointer protocol, + ) { + return __objc_msgSend_491( + obj, + sel, + session, + webSocketTask, + protocol, + ); + } + + late final __objc_msgSend_491Ptr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>('objc_msgSend'); + late final __objc_msgSend_491 = __objc_msgSend_491Ptr.asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>(); + + late final _sel_protocol1 = _registerName1("protocol"); + late final _class_CUPHTTPForwardedWebSocketClosed1 = + _getClass1("CUPHTTPForwardedWebSocketClosed"); + late final _sel_initWithSession_webSocketTask_didCloseWithCode_reason_1 = + _registerName1("initWithSession:webSocketTask:didCloseWithCode:reason:"); + ffi.Pointer _objc_msgSend_492( + ffi.Pointer obj, + ffi.Pointer sel, + ffi.Pointer session, + ffi.Pointer webSocketTask, + int closeCode, + ffi.Pointer reason, + ) { + return __objc_msgSend_492( + obj, + sel, + session, + webSocketTask, + closeCode, + reason, + ); + } + + late final __objc_msgSend_492Ptr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Int32, + ffi.Pointer)>>('objc_msgSend'); + late final __objc_msgSend_492 = __objc_msgSend_492Ptr.asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + int, + ffi.Pointer)>(); /// Creates a `Dart_CObject` containing the given `NSObject` pointer as an int. Dart_CObject NSObjectToCObject( @@ -83755,6 +83831,8 @@ abstract class MessageType { static const int CompletedMessage = 2; static const int RedirectMessage = 3; static const int FinishedDownloading = 4; + static const int WebSocketOpened = 5; + static const int WebSocketClosed = 6; } /// The configuration associated with a NSURLSessionTask. @@ -84305,6 +84383,136 @@ class CUPHTTPForwardedFinishedDownloading extends CUPHTTPForwardedDelegate { } } +class CUPHTTPForwardedWebSocketOpened extends CUPHTTPForwardedDelegate { + CUPHTTPForwardedWebSocketOpened._( + ffi.Pointer id, NativeCupertinoHttp lib, + {bool retain = false, bool release = false}) + : super._(id, lib, retain: retain, release: release); + + /// Returns a [CUPHTTPForwardedWebSocketOpened] that points to the same underlying object as [other]. + static CUPHTTPForwardedWebSocketOpened castFrom( + T other) { + return CUPHTTPForwardedWebSocketOpened._(other._id, other._lib, + retain: true, release: true); + } + + /// Returns a [CUPHTTPForwardedWebSocketOpened] that wraps the given raw object pointer. + static CUPHTTPForwardedWebSocketOpened castFromPointer( + NativeCupertinoHttp lib, ffi.Pointer other, + {bool retain = false, bool release = false}) { + return CUPHTTPForwardedWebSocketOpened._(other, lib, + retain: retain, release: release); + } + + /// Returns whether [obj] is an instance of [CUPHTTPForwardedWebSocketOpened]. + static bool isInstance(_ObjCWrapper obj) { + return obj._lib._objc_msgSend_0(obj._id, obj._lib._sel_isKindOfClass_1, + obj._lib._class_CUPHTTPForwardedWebSocketOpened1); + } + + NSObject initWithSession_webSocketTask_didOpenWithProtocol_( + NSURLSession? session, + NSURLSessionWebSocketTask? webSocketTask, + NSString? protocol) { + final _ret = _lib._objc_msgSend_491( + _id, + _lib._sel_initWithSession_webSocketTask_didOpenWithProtocol_1, + session?._id ?? ffi.nullptr, + webSocketTask?._id ?? ffi.nullptr, + protocol?._id ?? ffi.nullptr); + return NSObject._(_ret, _lib, retain: true, release: true); + } + + NSString? get protocol { + final _ret = _lib._objc_msgSend_32(_id, _lib._sel_protocol1); + return _ret.address == 0 + ? null + : NSString._(_ret, _lib, retain: true, release: true); + } + + static CUPHTTPForwardedWebSocketOpened new1(NativeCupertinoHttp _lib) { + final _ret = _lib._objc_msgSend_2( + _lib._class_CUPHTTPForwardedWebSocketOpened1, _lib._sel_new1); + return CUPHTTPForwardedWebSocketOpened._(_ret, _lib, + retain: false, release: true); + } + + static CUPHTTPForwardedWebSocketOpened alloc(NativeCupertinoHttp _lib) { + final _ret = _lib._objc_msgSend_2( + _lib._class_CUPHTTPForwardedWebSocketOpened1, _lib._sel_alloc1); + return CUPHTTPForwardedWebSocketOpened._(_ret, _lib, + retain: false, release: true); + } +} + +class CUPHTTPForwardedWebSocketClosed extends CUPHTTPForwardedDelegate { + CUPHTTPForwardedWebSocketClosed._( + ffi.Pointer id, NativeCupertinoHttp lib, + {bool retain = false, bool release = false}) + : super._(id, lib, retain: retain, release: release); + + /// Returns a [CUPHTTPForwardedWebSocketClosed] that points to the same underlying object as [other]. + static CUPHTTPForwardedWebSocketClosed castFrom( + T other) { + return CUPHTTPForwardedWebSocketClosed._(other._id, other._lib, + retain: true, release: true); + } + + /// Returns a [CUPHTTPForwardedWebSocketClosed] that wraps the given raw object pointer. + static CUPHTTPForwardedWebSocketClosed castFromPointer( + NativeCupertinoHttp lib, ffi.Pointer other, + {bool retain = false, bool release = false}) { + return CUPHTTPForwardedWebSocketClosed._(other, lib, + retain: retain, release: release); + } + + /// Returns whether [obj] is an instance of [CUPHTTPForwardedWebSocketClosed]. + static bool isInstance(_ObjCWrapper obj) { + return obj._lib._objc_msgSend_0(obj._id, obj._lib._sel_isKindOfClass_1, + obj._lib._class_CUPHTTPForwardedWebSocketClosed1); + } + + NSObject initWithSession_webSocketTask_didCloseWithCode_reason_( + NSURLSession? session, + NSURLSessionWebSocketTask? webSocketTask, + int closeCode, + NSData? reason) { + final _ret = _lib._objc_msgSend_492( + _id, + _lib._sel_initWithSession_webSocketTask_didCloseWithCode_reason_1, + session?._id ?? ffi.nullptr, + webSocketTask?._id ?? ffi.nullptr, + closeCode, + reason?._id ?? ffi.nullptr); + return NSObject._(_ret, _lib, retain: true, release: true); + } + + int get closeCode { + return _lib._objc_msgSend_424(_id, _lib._sel_closeCode1); + } + + NSData? get reason { + final _ret = _lib._objc_msgSend_51(_id, _lib._sel_reason1); + return _ret.address == 0 + ? null + : NSData._(_ret, _lib, retain: true, release: true); + } + + static CUPHTTPForwardedWebSocketClosed new1(NativeCupertinoHttp _lib) { + final _ret = _lib._objc_msgSend_2( + _lib._class_CUPHTTPForwardedWebSocketClosed1, _lib._sel_new1); + return CUPHTTPForwardedWebSocketClosed._(_ret, _lib, + retain: false, release: true); + } + + static CUPHTTPForwardedWebSocketClosed alloc(NativeCupertinoHttp _lib) { + final _ret = _lib._objc_msgSend_2( + _lib._class_CUPHTTPForwardedWebSocketClosed1, _lib._sel_alloc1); + return CUPHTTPForwardedWebSocketClosed._(_ret, _lib, + retain: false, release: true); + } +} + const int noErr = 0; const int kNilOptions = 0; diff --git a/pkgs/cupertino_http/src/CUPHTTPClientDelegate.h b/pkgs/cupertino_http/src/CUPHTTPClientDelegate.h index be1d0e2d08..775b70af56 100644 --- a/pkgs/cupertino_http/src/CUPHTTPClientDelegate.h +++ b/pkgs/cupertino_http/src/CUPHTTPClientDelegate.h @@ -19,6 +19,8 @@ typedef NS_ENUM(NSInteger, MessageType) { CompletedMessage = 2, RedirectMessage = 3, FinishedDownloading = 4, + WebSocketOpened = 5, + WebSocketClosed = 6, }; /** diff --git a/pkgs/cupertino_http/src/CUPHTTPClientDelegate.m b/pkgs/cupertino_http/src/CUPHTTPClientDelegate.m index e04d3f3a8c..b89b93c076 100644 --- a/pkgs/cupertino_http/src/CUPHTTPClientDelegate.m +++ b/pkgs/cupertino_http/src/CUPHTTPClientDelegate.m @@ -46,7 +46,8 @@ - (void)dealloc { [super dealloc]; } -- (void)registerTask:(NSURLSessionTask *) task withConfiguration:(CUPHTTPTaskConfiguration *)config { +- (void)registerTask:(NSURLSessionTask *) task + withConfiguration:(CUPHTTPTaskConfiguration *)config { [taskConfigurations setObject:config forKey:task]; } @@ -155,7 +156,7 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task } - (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)downloadTask + downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { CUPHTTPTaskConfiguration *config = [taskConfigurations objectForKey:downloadTask]; NSAssert(config != nil, @"No configuration for task."); @@ -213,4 +214,64 @@ - (void)URLSession:(NSURLSession *)session [forwardedComplete release]; } +// https://developer.apple.com/documentation/foundation/nsurlsessionwebsocketdelegate?language=objc + + +- (void)URLSession:(NSURLSession *)session + webSocketTask:(NSURLSessionWebSocketTask *)task +didOpenWithProtocol:(NSString *)protocol { + CUPHTTPTaskConfiguration *config = [taskConfigurations objectForKey:task]; + NSAssert(config != nil, @"No configuration for task."); + + CUPHTTPForwardedWebSocketOpened *opened = [[CUPHTTPForwardedWebSocketOpened alloc] + initWithSession:session webSocketTask:task + didOpenWithProtocol: protocol]; + + Dart_CObject ctype = MessageTypeToCObject(WebSocketOpened); + Dart_CObject cComplete = NSObjectToCObject(opened); + Dart_CObject* message_carray[] = { &ctype, &cComplete }; + + Dart_CObject message_cobj; + message_cobj.type = Dart_CObject_kArray; + message_cobj.value.as_array.length = 2; + message_cobj.value.as_array.values = message_carray; + + [opened.lock lock]; // After this line, any attempt to acquire the lock will wait. + const bool success = Dart_PostCObject_DL(config.sendPort, &message_cobj); + NSAssert(success, @"Dart_PostCObject_DL failed."); + + [opened.lock lock]; + [opened release]; +} + + +- (void)URLSession:(NSURLSession *)session + webSocketTask:(NSURLSessionWebSocketTask *)task + didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode + reason:(NSData *)reason { + CUPHTTPTaskConfiguration *config = [taskConfigurations objectForKey:task]; + NSAssert(config != nil, @"No configuration for task."); + + CUPHTTPForwardedWebSocketClosed *closed = [[CUPHTTPForwardedWebSocketClosed alloc] + initWithSession:session webSocketTask:task + code: closeCode + reason: reason]; + + Dart_CObject ctype = MessageTypeToCObject(WebSocketClosed); + Dart_CObject cComplete = NSObjectToCObject(closed); + Dart_CObject* message_carray[] = { &ctype, &cComplete }; + + Dart_CObject message_cobj; + message_cobj.type = Dart_CObject_kArray; + message_cobj.value.as_array.length = 2; + message_cobj.value.as_array.values = message_carray; + + [closed.lock lock]; // After this line, any attempt to acquire the lock will wait. + const bool success = Dart_PostCObject_DL(config.sendPort, &message_cobj); + NSAssert(success, @"Dart_PostCObject_DL failed."); + + [closed.lock lock]; + [closed release]; +} + @end diff --git a/pkgs/cupertino_http/src/CUPHTTPForwardedDelegate.h b/pkgs/cupertino_http/src/CUPHTTPForwardedDelegate.h index 7e6ce336a2..9e76ca80fb 100644 --- a/pkgs/cupertino_http/src/CUPHTTPForwardedDelegate.h +++ b/pkgs/cupertino_http/src/CUPHTTPForwardedDelegate.h @@ -106,3 +106,25 @@ @property (readonly) NSURL* location; @end + +@interface CUPHTTPForwardedWebSocketOpened : CUPHTTPForwardedDelegate + +- (id) initWithSession:(NSURLSession *)session + webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask + didOpenWithProtocol:(NSString *)protocol; + +@property (readonly) NSString* protocol; + +@end + +@interface CUPHTTPForwardedWebSocketClosed : CUPHTTPForwardedDelegate + +- (id) initWithSession:(NSURLSession *)session + webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask + didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode + reason:(NSData *)reason; + +@property (readonly) NSURLSessionWebSocketCloseCode closeCode; +@property (readonly) NSData* reason; + +@end diff --git a/pkgs/cupertino_http/src/CUPHTTPForwardedDelegate.m b/pkgs/cupertino_http/src/CUPHTTPForwardedDelegate.m index 7ffa812fe9..03e413d5db 100644 --- a/pkgs/cupertino_http/src/CUPHTTPForwardedDelegate.m +++ b/pkgs/cupertino_http/src/CUPHTTPForwardedDelegate.m @@ -140,3 +140,44 @@ - (void) dealloc { } @end + +@implementation CUPHTTPForwardedWebSocketOpened + +- (id) initWithSession:(NSURLSession *)session + webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask + didOpenWithProtocol:(NSString *)protocol { + self = [super initWithSession: session task: webSocketTask]; + if (self != nil) { + self->_protocol = [protocol retain]; + } + return self; +} + +- (void) dealloc { + [self->_protocol release]; + [super dealloc]; +} + +@end + +@implementation CUPHTTPForwardedWebSocketClosed + +- (id) initWithSession:(NSURLSession *)session + webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask + code:(NSURLSessionWebSocketCloseCode)closeCode + reason:(NSData *)reason { + self = [super initWithSession: session task: webSocketTask]; + if (self != nil) { + self->_closeCode = closeCode; + self->_reason = [reason retain]; + } + return self; +} + +- (void) dealloc { + [self->_reason release]; + [super dealloc]; +} + +@end +