Skip to content

Commit

Permalink
Submit
Browse files Browse the repository at this point in the history
  • Loading branch information
brianquinlan committed Jan 31, 2024
1 parent 26d0df1 commit a6833bc
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 66 deletions.
6 changes: 3 additions & 3 deletions pkgs/cupertino_http/lib/src/websocket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand All @@ -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');
}
Expand Down
25 changes: 7 additions & 18 deletions pkgs/web_socket_conformance_tests/lib/src/close_local_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<XXXWebSocketException>()));
});

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<XXXWebSocketException>()));
});
*/

test('with code and reason', () async {
final channel = await channelFactory(uri);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,44 +40,44 @@ 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])));
});

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));
});

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])));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions pkgs/websocket/lib/htmlwebsocket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ class HtmlWebSocket implements XXXWebSocket {
print('closing with $code, $reason');
if (!_events.isClosed) {
_events
..add(Closed(code, reason))
..add(CloseReceived(code, reason))
..close();
}
}

HtmlWebSocket._(this._webSocket);

@override
void addBytes(Uint8List b) {
void sendBytes(Uint8List b) {
if (_events.isClosed) {
throw StateError('WebSocket is closed');
}
Expand All @@ -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');
}
Expand Down
13 changes: 9 additions & 4 deletions pkgs/websocket/lib/iowebsocket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
},
Expand All @@ -48,15 +49,15 @@ 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');
}
_webSocket.add(b);
}

@override
void addString(String s) {
void sendText(String s) {
if (_events.isClosed) {
throw StateError('WebSocket is closed');
}
Expand All @@ -81,7 +82,11 @@ class IOWebSocket implements XXXWebSocket {
Future<void> 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);
}
}
}

Expand Down
73 changes: 51 additions & 22 deletions pkgs/websocket/lib/websocket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -39,59 +43,84 @@ 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 {
final String message;
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.
///
/// Set the optional code and reason arguments to send close information
/// 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<void> 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).
///
Expand Down

0 comments on commit a6833bc

Please sign in to comment.