diff --git a/pkgs/cupertino_http/lib/src/websocket.dart b/pkgs/cupertino_http/lib/src/websocket.dart index 2f6978e641..7191d4488c 100644 --- a/pkgs/cupertino_http/lib/src/websocket.dart +++ b/pkgs/cupertino_http/lib/src/websocket.dart @@ -99,13 +99,13 @@ class CupertinoWebSocket implements XXXWebSocket { final closeReason = reason == null ? null : utf8.decode(reason.bytes); _events - ..add(Closed(closeCode, closeReason)) + ..add(CloseReceived(closeCode, closeReason)) ..close(); } } @override - void addBytes(Uint8List b) { + void sendBytes(Uint8List b) { if (_events.isClosed) { throw StateError('WebSocket is closed'); } @@ -115,7 +115,7 @@ class CupertinoWebSocket implements XXXWebSocket { } @override - void addString(String s) { + void sendText(String s) { if (_events.isClosed) { throw StateError('WebSocket is closed'); } diff --git a/pkgs/web_socket_conformance_tests/lib/src/close_local_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/close_local_tests.dart index c11747023d..149f94e02f 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/close_local_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/close_local_tests.dart @@ -44,32 +44,21 @@ void testLocalClose( // Dart will wait up to 5 seconds to get the close code from the server otherwise // it will use the local close code. -/* test('reserved close code', () async { // If code is present, but is neither an integer equal to 1000 nor an integer in the range 3000 to 4999, inclusive, throw an "InvalidAccessError" DOMException. // If reasonBytes is longer than 123 bytes, then throw a "SyntaxError" DOMException. - final channel = channelFactory(uri); - - await expectLater(channel.ready, completes); - expect(channel.closeCode, null); - expect(channel.closeReason, null); - // web uncaught // InvalidAccessError - // sync WebSocketException - await channel.sink.close(1004, 'boom'); + final channel = await channelFactory(uri); + await expectLater( + () => channel.close(1004), throwsA(isA())); }); test('too long close reason', () async { - final channel = channelFactory(uri); - - await expectLater(channel.ready, completes); - expect(channel.closeCode, null); - expect(channel.closeReason, null); - // web uncaught // SyntaxError - // vm: passes! - await channel.sink.close(1000, 'Boom'.padLeft(1000)); + final channel = await channelFactory(uri); + await expectLater(() => channel.close(3000, 'Boom'.padLeft(1000)), + throwsA(isA())); }); -*/ + test('with code and reason', () async { final channel = await channelFactory(uri); diff --git a/pkgs/web_socket_conformance_tests/lib/src/close_remote_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/close_remote_tests.dart index ed2564b6ba..daade10793 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/close_remote_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/close_remote_tests.dart @@ -73,18 +73,18 @@ void testRemoteClose( test('with code and reason', () async { final channel = await channelFactory(uri); - channel.addString('Please close'); + channel.sendText('Please close'); expect(await channel.events.toList(), - [Closed(4123, 'server closed the connection')]); + [CloseReceived(4123, 'server closed the connection')]); }); test('send after close', () async { final channel = await channelFactory(uri); - channel.addString('Please close'); + channel.sendText('Please close'); expect(await channel.events.toList(), - [Closed(4123, 'server closed the connection')]); - expect(() => channel.addString('test'), throwsStateError); + [CloseReceived(4123, 'server closed the connection')]); + expect(() => channel.sendText('test'), throwsStateError); /* final closeCode = await httpServerQueue.next as int?; final closeReason = await httpServerQueue.next as String?; diff --git a/pkgs/web_socket_conformance_tests/lib/src/disconnect_after_upgrade_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/disconnect_after_upgrade_tests.dart index c6cd7bd0d0..f114205bf6 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/disconnect_after_upgrade_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/disconnect_after_upgrade_tests.dart @@ -29,9 +29,9 @@ void testDisconnectAfterUpgrade( test('disconnect after upgrade', () async { final channel = await channelFactory(uri); - channel.addString('test'); + channel.sendText('test'); expect( - (await channel.events.single as Closed).code, + (await channel.events.single as CloseReceived).code, anyOf([ 1005, // closed no status 1006, // closed abnormal diff --git a/pkgs/web_socket_conformance_tests/lib/src/payload_transfer_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/payload_transfer_tests.dart index df581917c2..70245459ce 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/payload_transfer_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/payload_transfer_tests.dart @@ -40,28 +40,28 @@ void testPayloadTransfer( test('empty string request and response', () async { final channel = await channelFactory(uri); - channel.addString(''); + channel.sendText(''); expect(await channel.events.first, TextDataReceived('')); }); test('empty binary request and response', () async { final channel = await channelFactory(uri); - channel.addBytes(Uint8List(0)); + channel.sendBytes(Uint8List(0)); expect(await channel.events.first, BinaryDataReceived(Uint8List(0))); }); test('string request and response', () async { final channel = await channelFactory(uri); - channel.addString("Hello World!"); + channel.sendText("Hello World!"); expect(await channel.events.first, TextDataReceived("Hello World!")); }); test('binary request and response', () async { final channel = await channelFactory(uri); - channel.addBytes(Uint8List.fromList([1, 2, 3, 4, 5])); + channel.sendBytes(Uint8List.fromList([1, 2, 3, 4, 5])); expect(await channel.events.first, BinaryDataReceived(Uint8List.fromList([1, 2, 3, 4, 5]))); }); @@ -69,7 +69,7 @@ void testPayloadTransfer( test('large string request and response', () async { final channel = await channelFactory(uri); - channel.addString("Hello World!" * 1000); + channel.sendText("Hello World!" * 1000); expect( await channel.events.first, TextDataReceived("Hello World!" * 1000)); }); @@ -77,7 +77,7 @@ void testPayloadTransfer( test('large binary request and response - XXX', () async { final channel = await channelFactory(uri); - channel.addBytes(Uint8List.fromList([1, 2, 3, 4, 5])); + channel.sendBytes(Uint8List.fromList([1, 2, 3, 4, 5])); expect(await channel.events.first, BinaryDataReceived(Uint8List.fromList([1, 2, 3, 4, 5]))); }); diff --git a/pkgs/web_socket_conformance_tests/lib/src/peer_protocol_errors_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/peer_protocol_errors_tests.dart index 27e440687d..fd6abd7e2a 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/peer_protocol_errors_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/peer_protocol_errors_tests.dart @@ -32,7 +32,7 @@ void testPeerProtocolErrors( test('bad data after upgrade', () async { final channel = await channelFactory(uri); expect( - (await channel.events.single as Closed).code, + (await channel.events.single as CloseReceived).code, anyOf([ 1002, // protocol error 1005, // closed no status @@ -42,9 +42,9 @@ void testPeerProtocolErrors( test('bad data after upgrade with write', () async { final channel = await channelFactory(uri); - channel.addString('test'); + channel.sendText('test'); expect( - (await channel.events.single as Closed).code, + (await channel.events.single as CloseReceived).code, anyOf([ 1002, // protocol error 1005, // closed no status diff --git a/pkgs/websocket/lib/htmlwebsocket.dart b/pkgs/websocket/lib/htmlwebsocket.dart index a513353db1..17ab4b1ba8 100644 --- a/pkgs/websocket/lib/htmlwebsocket.dart +++ b/pkgs/websocket/lib/htmlwebsocket.dart @@ -74,7 +74,7 @@ class HtmlWebSocket implements XXXWebSocket { print('closing with $code, $reason'); if (!_events.isClosed) { _events - ..add(Closed(code, reason)) + ..add(CloseReceived(code, reason)) ..close(); } } @@ -82,7 +82,7 @@ class HtmlWebSocket implements XXXWebSocket { HtmlWebSocket._(this._webSocket); @override - void addBytes(Uint8List b) { + void sendBytes(Uint8List b) { if (_events.isClosed) { throw StateError('WebSocket is closed'); } @@ -91,7 +91,7 @@ class HtmlWebSocket implements XXXWebSocket { } @override - void addString(String s) { + void sendText(String s) { if (_events.isClosed) { throw StateError('WebSocket is closed'); } diff --git a/pkgs/websocket/lib/iowebsocket.dart b/pkgs/websocket/lib/iowebsocket.dart index ad2ed152de..9c5baa4814 100644 --- a/pkgs/websocket/lib/iowebsocket.dart +++ b/pkgs/websocket/lib/iowebsocket.dart @@ -39,7 +39,8 @@ class IOWebSocket implements XXXWebSocket { onDone: () { print('onDone'); if (!_events.isClosed) { - _events.add(Closed(_webSocket.closeCode, _webSocket.closeReason)); + _events + .add(CloseReceived(_webSocket.closeCode, _webSocket.closeReason)); _events.close(); } }, @@ -48,7 +49,7 @@ class IOWebSocket implements XXXWebSocket { // JS: Silently discards data if connection is closed. @override - void addBytes(Uint8List b) { + void sendBytes(Uint8List b) { if (_events.isClosed) { throw StateError('WebSocket is closed'); } @@ -56,7 +57,7 @@ class IOWebSocket implements XXXWebSocket { } @override - void addString(String s) { + void sendText(String s) { if (_events.isClosed) { throw StateError('WebSocket is closed'); } @@ -81,7 +82,11 @@ class IOWebSocket implements XXXWebSocket { Future close([int? code, String? reason]) async { if (!_events.isClosed) { unawaited(_events.close()); - await _webSocket.close(code, reason); + try { + await _webSocket.close(code, reason); + } on io.WebSocketException catch (e) { + throw XXXWebSocketException(e.message); + } } } diff --git a/pkgs/websocket/lib/websocket.dart b/pkgs/websocket/lib/websocket.dart index 69f51e0bce..59def858e4 100644 --- a/pkgs/websocket/lib/websocket.dart +++ b/pkgs/websocket/lib/websocket.dart @@ -3,8 +3,10 @@ import 'dart:typed_data'; sealed class WebSocketEvent {} -/// A received text frame. -class TextDataReceived extends WebSocketEvent { +/// Text data received by the peer. +/// +/// See [XXXWebSocket.events]. +final class TextDataReceived extends WebSocketEvent { final String text; TextDataReceived(this.text); @@ -16,8 +18,10 @@ class TextDataReceived extends WebSocketEvent { int get hashCode => text.hashCode; } -// A received binary frame. -class BinaryDataReceived extends WebSocketEvent { +/// Binary data received by the peer. +/// +/// See [XXXWebSocket.events]. +final class BinaryDataReceived extends WebSocketEvent { final Uint8List data; BinaryDataReceived(this.data); @@ -39,22 +43,25 @@ class BinaryDataReceived extends WebSocketEvent { String toString() => 'BinaryDataReceived($data)'; } -/// A received close frame or failure. -class Closed extends WebSocketEvent { +/// A close notification sent from the peer or a failure indication. +/// +/// See [XXXWebSocket.events]. +final class CloseReceived extends WebSocketEvent { + /// See [RFC-6455 7.4](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4) final int? code; final String? reason; - Closed([this.code, this.reason]); + CloseReceived([this.code, this.reason]); @override bool operator ==(Object other) => - other is Closed && other.code == code && other.reason == reason; + other is CloseReceived && other.code == code && other.reason == reason; @override int get hashCode => [code, reason].hashCode; @override - String toString() => 'Closed($code, $reason)'; + String toString() => 'CloseReceived($code, $reason)'; } class XXXWebSocketException implements Exception { @@ -62,17 +69,29 @@ class XXXWebSocketException implements Exception { XXXWebSocketException([this.message = ""]); } -class WebSocketConnectionClosed extends XXXWebSocketException { - WebSocketConnectionClosed([super.message = 'Connection Closed']); +/// Thrown if [XXXWebSocket.sendText] or [XXXWebSocket.sendBytes] is called +/// when the [XXXWebSocket] is closed. +class XXXWebSocketConnectionClosed extends XXXWebSocketException { + XXXWebSocketConnectionClosed([super.message = 'Connection Closed']); } /// What's a good name for this? `SimpleWebSocket`? 'LCDWebSocket`? abstract interface class XXXWebSocket { - /// Throws [WebSocketConnectionClosed] if the [XXXWebSocket] is closed (either through [close] or by the peer). - void addString(String s); - - /// Throws [WebSocketConnectionClosed] if the [XXXWebSocket] is closed (either through [close] or by the peer). - void addBytes(Uint8List b); + /// Say something about not guaranteeing delivery. + /// + /// Throws [XXXWebSocketConnectionClosed] if the [XXXWebSocket] is closed + /// (either through [close] or by the peer). Alternatively, we could just throw + /// the data away - that's what JavaScript does. Probably that is better + /// so every call to [sendText], [sendBytes] and [close] doesn't need to be + /// surrounded in a try block. + void sendText(String s); + + /// Say something about not guaranteeing delivery. + /// + /// Throws [XXXWebSocketConnectionClosed] if the [XXXWebSocket] is closed + /// (either through [close] or by the peer). Alternatively, we could just throw + /// the data away - that's what JavaScript does. + void sendBytes(Uint8List b); /// Closes the WebSocket connection. /// @@ -80,18 +99,28 @@ abstract interface class XXXWebSocket { /// to the peer. If they are omitted, the peer will see a 1005 status code /// with no reason. /// + /// If [code] is not in the range 3000-4999 then an [ArgumentError] + /// will be thrown. + /// + /// If [reason] is longer than 123 bytes when encoded as UTF-8 then + /// [ArgumentError] will be thrown. + /// /// [events] will be closed. + /// + /// Throws [XXXWebSocketConnectionClosed] if the connection is already closed + /// (including by the peer). Alternatively, we could just throw the close + /// away. Future close([int? code, String? reason]); /// Events received from the peer. /// - /// If a [Closed] event is received then the [Stream] will be closed. A - /// [Closed] event indicates either that: + /// If a [CloseReceived] event is received then the [Stream] will be closed. A + /// [CloseReceived] event indicates either that: /// - /// - A close frame was received from the peer. [Closed.code] and - /// [Closed.reason] will be set by the peer. - /// - A failure occured (e.g. the peer disconnected). [Closed.code] and - /// [Closed.reason] will be a failure code defined by + /// - A close frame was received from the peer. [CloseReceived.code] and + /// [CloseReceived.reason] will be set by the peer. + /// - A failure occured (e.g. the peer disconnected). [CloseReceived.code] and + /// [CloseReceived.reason] will be a failure code defined by /// (RFC-6455)[https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1] /// (e.g. 1006). ///