Skip to content

Commit

Permalink
WebSockets work!
Browse files Browse the repository at this point in the history
  • Loading branch information
brianquinlan committed Oct 2, 2024
1 parent 7f6608d commit 868b929
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 139 deletions.
4 changes: 4 additions & 0 deletions pkgs/cupertino_http/ffigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ preamble: |
comments:
style: any
length: full
enums:
as-int:
include:
- 'NSURLSessionWebSocketCloseCode'
2 changes: 1 addition & 1 deletion pkgs/cupertino_http/lib/cupertino_http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,4 @@ import 'src/cupertino_client.dart';

export 'src/cupertino_api.dart';
export 'src/cupertino_client.dart' show CupertinoClient;
// export 'src/cupertino_web_socket.dart';
export 'src/cupertino_web_socket.dart';
82 changes: 31 additions & 51 deletions pkgs/cupertino_http/lib/src/cupertino_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@
library;

import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';

import 'package:async/async.dart';
import 'package:objective_c/objective_c.dart' as objc;

import 'native_cupertino_bindings.dart' as ncb;
Expand Down Expand Up @@ -597,8 +595,7 @@ class URLSessionWebSocketTask extends URLSessionTask {
/// The close code set when the WebSocket connection is closed.
///
/// See [NSURLSessionWebSocketTask.closeCode](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask/3181201-closecode)
ncb.NSURLSessionWebSocketCloseCode get closeCode =>
_urlSessionWebSocketTask.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.
Expand All @@ -613,28 +610,17 @@ class URLSessionWebSocketTask extends URLSessionTask {
///
/// See [NSURLSessionWebSocketTask.sendMessage:completionHandler:](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask/3181205-sendmessage)
Future<void> sendMessage(URLSessionWebSocketMessage message) async {
/*
_urlSessionWebSocketTask.sendMessage_completionHandler_(
message._nsObject, (error) error);
final completer = Completer<void>();
final completionPort = ReceivePort();
completionPort.listen((message) {
final ep = Pointer<objc.ObjCObject>.fromAddress(message as int);
if (ep.address == 0) {
_urlSessionWebSocketTask.sendMessage_completionHandler_(message._nsObject,
ncb.ObjCBlock_ffiVoid_NSError.listener((error) {
if (error == null) {
completer.complete();
} else {
final error =
objc.NSError.castFromPointer(ep, retain: false, release: true);
completer.completeError(error);
}
completionPort.close();
});
}));

helperLibs.CUPHTTPSendMessage(_urlSessionWebSocketTask, message._nsObject,
completionPort.sendPort.nativePort);
await completer.future;
*/
}

/// Receives a single WebSocket message.
Expand All @@ -644,45 +630,25 @@ class URLSessionWebSocketTask extends URLSessionTask {
/// 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 = messageAndError[0] as int;
final ep = messageAndError[1] as int;

final message = mp == 0
? null
: URLSessionWebSocketMessage._(
ncb.NSURLSessionWebSocketMessage.castFromPointer(
Pointer.fromAddress(mp).cast<objc.ObjCObject>(),
retain: false,
release: true));
final error = ep == 0
? null
: objc.NSError.castFromPointer(
Pointer.fromAddress(ep).cast<objc.ObjCObject>(),
retain: false,
release: true);

_urlSessionWebSocketTask.receiveMessageWithCompletionHandler_(
ncb.ObjCBlock_ffiVoid_NSURLSessionWebSocketMessage_NSError.listener(
(message, error) {
if (error != null) {
completer.completeError(error);
} else if (message != null) {
completer.complete(URLSessionWebSocketMessage._(message));
} else {
completer.complete(message!);
completer.completeError(
StateError('one of message or error must be non-null'));
}
completionPort.close();
});

// helperLibs.CUPHTTPReceiveMessage(
// _urlSessionWebSocketTask, 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(
ncb.NSURLSessionWebSocketCloseCode closeCode, objc.NSData? reason) {
void cancelWithCloseCode(int closeCode, objc.NSData? reason) {
_urlSessionWebSocketTask.cancelWithCloseCode_reason_(closeCode, reason);
}

Expand Down Expand Up @@ -868,7 +834,7 @@ class URLSession extends _ObjectHolder<ncb.NSURLSession> {
URLSession session, URLSessionWebSocketTask task, String? protocol)?
onWebSocketTaskOpened,
void Function(URLSession session, URLSessionWebSocketTask task,
ncb.NSURLSessionWebSocketCloseCode closeCode, objc.NSData? reason)?
int closeCode, objc.NSData? reason)?
onWebSocketTaskClosed,
}) {
final protoBuilder = objc.ObjCProtocolBuilder();
Expand Down Expand Up @@ -938,6 +904,21 @@ class URLSession extends _ObjectHolder<ncb.NSURLSession> {
nsurlToUri(nsUrl));
}
});

ncb.NSURLSessionWebSocketDelegate.addToBuilderAsListener(protoBuilder,
URLSession_webSocketTask_didOpenWithProtocol_:
(session, task, protocol) {
if (onWebSocketTaskOpened != null) {
onWebSocketTaskOpened(URLSession._(session, isBackground: isBackground),
URLSessionWebSocketTask._(task), protocol?.toString());
}
}, URLSession_webSocketTask_didCloseWithCode_reason_:
(session, task, closeCode, reason) {
if (onWebSocketTaskClosed != null) {
onWebSocketTaskClosed(URLSession._(session, isBackground: isBackground),
URLSessionWebSocketTask._(task), closeCode, reason);
}
});
return protoBuilder.build();
}

Expand Down Expand Up @@ -1011,7 +992,7 @@ class URLSession extends _ObjectHolder<ncb.NSURLSession> {
URLSession session, URLSessionWebSocketTask task, String? protocol)?
onWebSocketTaskOpened,
void Function(URLSession session, URLSessionWebSocketTask task,
ncb.NSURLSessionWebSocketCloseCode? closeCode, objc.NSData? reason)?
int? closeCode, objc.NSData? reason)?
onWebSocketTaskClosed,
}) {
// Avoid the complexity of simultaneous or out-of-order delegate callbacks
Expand Down Expand Up @@ -1148,7 +1129,6 @@ class URLSession extends _ObjectHolder<ncb.NSURLSession> {
throw UnsupportedError(
'WebSocket tasks are not supported in background sessions');
}

final URLSessionWebSocketTask task;
if (protocols == null) {
task = URLSessionWebSocketTask._(
Expand Down
37 changes: 21 additions & 16 deletions pkgs/cupertino_http/lib/src/cupertino_web_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import 'dart:convert';
import 'dart:typed_data';

import 'package:web_socket/web_socket.dart';
import 'package:objective_c/objective_c.dart' as objc;

import 'cupertino_api.dart';

/// An error occurred while connecting to the peer.
class ConnectionException extends WebSocketException {
final Error error;
final objc.NSError error;

ConnectionException(super.message, this.error);

Expand Down Expand Up @@ -115,7 +116,7 @@ class CupertinoWebSocket implements WebSocket {
// 3. an error occurred (e.g. network failure) and `_connectionClosed`
// will signal that and close `event`.
webSocket._connectionClosed(
1006, Data.fromList('abnormal close'.codeUnits));
1006, 'abnormal close'.codeUnits.toNSData());
}
});

Expand All @@ -138,11 +139,13 @@ class CupertinoWebSocket implements WebSocket {

late WebSocketEvent event;
switch (value.type) {
case URLSessionWebSocketMessageType.urlSessionWebSocketMessageTypeString:
case NSURLSessionWebSocketMessageType
.NSURLSessionWebSocketMessageTypeString:
event = TextDataReceived(value.string!);
break;
case URLSessionWebSocketMessageType.urlSessionWebSocketMessageTypeData:
event = BinaryDataReceived(value.data!.bytes);
case NSURLSessionWebSocketMessageType
.NSURLSessionWebSocketMessageTypeData:
event = BinaryDataReceived(value.data!.toList());
break;
}
_events.add(event);
Expand All @@ -158,28 +161,30 @@ class CupertinoWebSocket implements WebSocket {
/// Close the WebSocket connection due to an error and send the
/// [CloseReceived] event.
void _closeConnectionWithError(Object e) {
if (e is Error) {
if (e.domain == 'NSPOSIXErrorDomain' && e.code == 57) {
if (e is objc.NSError) {
if (e.domain.toString() == 'NSPOSIXErrorDomain' && e.code == 57) {
// Socket is not connected.
// onWebSocketTaskClosed/onComplete will be invoked and may indicate a
// close code.
return;
}
var (int code, String? reason) = switch ([e.domain, e.code]) {
['NSPOSIXErrorDomain', 100] => (1002, e.localizedDescription),
_ => (1006, e.localizedDescription)
var (int code, String? reason) = switch ([e.domain.toString(), e.code]) {
['NSPOSIXErrorDomain', 100] => (
1002,
e.localizedDescription.toString()
),
_ => (1006, e.localizedDescription.toString())
};
_task.cancel();
_connectionClosed(
code, reason == null ? null : Data.fromList(reason.codeUnits));
_connectionClosed(code, reason.codeUnits.toNSData());
} else {
throw StateError('unexpected error: $e');
}
}

void _connectionClosed(int? closeCode, Data? reason) {
void _connectionClosed(int? closeCode, objc.NSData? reason) {
if (!_events.isClosed) {
final closeReason = reason == null ? '' : utf8.decode(reason.bytes);
final closeReason = reason == null ? '' : utf8.decode(reason.toList());

_events
..add(CloseReceived(closeCode, closeReason))
Expand All @@ -193,7 +198,7 @@ class CupertinoWebSocket implements WebSocket {
throw WebSocketConnectionClosed();
}
_task
.sendMessage(URLSessionWebSocketMessage.fromData(Data.fromList(b)))
.sendMessage(URLSessionWebSocketMessage.fromData(b.toNSData()))
.then((value) => value, onError: _closeConnectionWithError);
}

Expand Down Expand Up @@ -226,7 +231,7 @@ class CupertinoWebSocket implements WebSocket {
unawaited(_events.close());
if (code != null) {
reason = reason ?? '';
_task.cancelWithCloseCode(code, Data.fromList(utf8.encode(reason)));
_task.cancelWithCloseCode(code, utf8.encode(reason).toNSData());
} else {
_task.cancel();
}
Expand Down
Loading

0 comments on commit 868b929

Please sign in to comment.