Skip to content

Commit c0ea76f

Browse files
authored
Implement IOWebSocketChannel as a WebSocketAdapterWebSocket subclass (dart-archive/web_socket_channel#342)
1 parent 510f396 commit c0ea76f

File tree

6 files changed

+61
-99
lines changed

6 files changed

+61
-99
lines changed

pkgs/web_socket_channel/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
default implementation for `WebSocketChannel.connect`.
55
- **BREAKING**: Remove `WebSocketChannel` constructor.
66
- **BREAKING**: Make `WebSocketChannel` an `abstract interface`.
7+
- **BREAKING**: `IOWebSocketChannel.ready` will throw
8+
`WebSocketChannelException` instead of `WebSocketException`.
79

810
## 2.4.5
911

pkgs/web_socket_channel/lib/io.dart

Lines changed: 16 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,15 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6-
import 'dart:io';
7-
8-
import 'package:async/async.dart';
9-
import 'package:stream_channel/stream_channel.dart';
6+
import 'dart:io' show HttpClient, WebSocket;
7+
import 'package:web_socket/io_web_socket.dart' as io_web_socket;
108

119
import 'src/channel.dart';
1210
import 'src/exception.dart';
13-
import 'src/sink_completer.dart';
11+
import 'web_socket_adapter_web_socket_channel.dart';
1412

1513
/// A [WebSocketChannel] that communicates using a `dart:io` [WebSocket].
16-
class IOWebSocketChannel extends StreamChannelMixin
17-
implements WebSocketChannel {
18-
/// The underlying `dart:io` [WebSocket].
19-
///
20-
/// If the channel was constructed with [IOWebSocketChannel.connect], this is
21-
/// `null` until the [WebSocket.connect] future completes.
22-
WebSocket? _webSocket;
23-
24-
@override
25-
String? get protocol => _webSocket?.protocol;
26-
27-
@override
28-
int? get closeCode => _webSocket?.closeCode;
29-
30-
@override
31-
String? get closeReason => _webSocket?.closeReason;
32-
33-
@override
34-
final Stream stream;
35-
36-
@override
37-
final WebSocketSink sink;
38-
39-
/// Completer for [ready].
40-
final Completer<void> _readyCompleter;
41-
42-
@override
43-
Future<void> get ready => _readyCompleter.future;
44-
45-
/// The underlying [WebSocket], if this channel has connected.
46-
///
47-
/// If the future returned from [WebSocket.connect] has not yet completed, or
48-
/// completed as an error, this will be null.
49-
WebSocket? get innerWebSocket => _webSocket;
50-
14+
class IOWebSocketChannel extends WebSocketAdapterWebSocketChannel {
5115
/// Creates a new WebSocket connection.
5216
///
5317
/// Connects to [url] using [WebSocket.connect] and returns a channel that can
@@ -76,58 +40,24 @@ class IOWebSocketChannel extends StreamChannelMixin
7640
Duration? connectTimeout,
7741
HttpClient? customClient,
7842
}) {
79-
late IOWebSocketChannel channel;
80-
final sinkCompleter = WebSocketSinkCompleter();
81-
var future = WebSocket.connect(
43+
var webSocketFuture = WebSocket.connect(
8244
url.toString(),
8345
headers: headers,
8446
protocols: protocols,
8547
customClient: customClient,
86-
);
48+
).then((webSocket) => webSocket..pingInterval = pingInterval);
49+
8750
if (connectTimeout != null) {
88-
future = future.timeout(connectTimeout);
51+
webSocketFuture = webSocketFuture.timeout(connectTimeout);
8952
}
90-
final stream = StreamCompleter.fromFuture(future.then((webSocket) {
91-
webSocket.pingInterval = pingInterval;
92-
channel._webSocket = webSocket;
93-
channel._readyCompleter.complete();
94-
sinkCompleter.setDestinationSink(_IOWebSocketSink(webSocket));
95-
return webSocket;
96-
}).catchError((Object error, StackTrace stackTrace) {
97-
channel._readyCompleter.completeError(error, stackTrace);
98-
throw WebSocketChannelException.from(error);
99-
}));
100-
return channel =
101-
IOWebSocketChannel._withoutSocket(stream, sinkCompleter.sink);
102-
}
103-
104-
/// Creates a channel wrapping [socket].
105-
IOWebSocketChannel(WebSocket socket)
106-
: _webSocket = socket,
107-
stream = socket.handleError(
108-
(Object? error) => throw WebSocketChannelException.from(error)),
109-
sink = _IOWebSocketSink(socket),
110-
_readyCompleter = Completer()..complete();
11153

112-
/// Creates a channel without a socket.
113-
///
114-
/// This is used with `connect` to synchronously provide a channel that later
115-
/// has a socket added.
116-
IOWebSocketChannel._withoutSocket(Stream stream, this.sink)
117-
: _webSocket = null,
118-
stream = stream.handleError(
119-
(Object? error) => throw WebSocketChannelException.from(error)),
120-
_readyCompleter = Completer();
121-
}
122-
123-
/// A [WebSocketSink] that forwards [close] calls to a `dart:io` [WebSocket].
124-
class _IOWebSocketSink extends DelegatingStreamSink implements WebSocketSink {
125-
/// The underlying socket.
126-
final WebSocket _webSocket;
127-
128-
_IOWebSocketSink(WebSocket super.webSocket) : _webSocket = webSocket;
54+
return IOWebSocketChannel(webSocketFuture);
55+
}
12956

130-
@override
131-
Future close([int? closeCode, String? closeReason]) =>
132-
_webSocket.close(closeCode, closeReason);
57+
/// Creates a channel wrapping [webSocket].
58+
IOWebSocketChannel(FutureOr<WebSocket> webSocket)
59+
: super(webSocket is Future<WebSocket>
60+
? webSocket.then(io_web_socket.IOWebSocket.fromWebSocket)
61+
as FutureOr<io_web_socket.IOWebSocket>
62+
: io_web_socket.IOWebSocket.fromWebSocket(webSocket));
13363
}

pkgs/web_socket_channel/lib/web_socket_adapter_web_socket_channel.dart

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,19 @@ class WebSocketAdapterWebSocketChannel extends StreamChannelMixin
6666
/// future will complete with an error.
6767
factory WebSocketAdapterWebSocketChannel.connect(Uri url,
6868
{Iterable<String>? protocols}) =>
69-
WebSocketAdapterWebSocketChannel._(
69+
WebSocketAdapterWebSocketChannel(
7070
WebSocket.connect(url, protocols: protocols));
7171

72-
// Create a [WebSocketWebSocketChannelAdapter] from an existing [WebSocket].
73-
factory WebSocketAdapterWebSocketChannel.fromWebSocket(WebSocket webSocket) =>
74-
WebSocketAdapterWebSocketChannel._(Future.value(webSocket));
72+
// Construct a [WebSocketWebSocketChannelAdapter] from an existing
73+
// [WebSocket].
74+
WebSocketAdapterWebSocketChannel(FutureOr<WebSocket> webSocket) {
75+
Future<WebSocket> webSocketFuture;
76+
if (webSocket is WebSocket) {
77+
webSocketFuture = Future.value(webSocket);
78+
} else {
79+
webSocketFuture = webSocket;
80+
}
7581

76-
WebSocketAdapterWebSocketChannel._(Future<WebSocket> webSocketFuture) {
7782
webSocketFuture.then((webSocket) {
7883
var remoteClosed = false;
7984
webSocket.events.listen((event) {
@@ -113,7 +118,16 @@ class WebSocketAdapterWebSocketChannel extends StreamChannelMixin
113118
_protocol = webSocket.protocol;
114119
_readyCompleter.complete();
115120
}, onError: (Object e) {
116-
_readyCompleter.completeError(WebSocketChannelException.from(e));
121+
Exception error;
122+
if (e is TimeoutException) {
123+
// Required for backwards compatibility with `IOWebSocketChannel`.
124+
error = e;
125+
} else {
126+
error = WebSocketChannelException.from(e);
127+
}
128+
_readyCompleter.completeError(error);
129+
_controller.local.sink.addError(error);
130+
_controller.local.sink.close();
117131
});
118132
}
119133
}

pkgs/web_socket_channel/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ dependencies:
1414
crypto: ^3.0.0
1515
stream_channel: ^2.1.0
1616
web: ^0.5.0
17-
web_socket: ^0.1.0
17+
web_socket: ^0.1.1
1818

1919
dev_dependencies:
2020
dart_flutter_team_lints: ^2.0.0

pkgs/web_socket_channel/test/io_test.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ void main() {
131131
});
132132

133133
final channel = IOWebSocketChannel.connect('ws://localhost:${server.port}');
134-
expect(channel.ready, throwsA(isA<WebSocketException>()));
134+
expect(channel.ready, throwsA(isA<WebSocketChannelException>()));
135135
expect(channel.stream.drain<void>(),
136136
throwsA(isA<WebSocketChannelException>()));
137137
});
@@ -154,7 +154,7 @@ void main() {
154154
'ws://localhost:${server.port}',
155155
protocols: [failedProtocol],
156156
);
157-
expect(channel.ready, throwsA(isA<WebSocketException>()));
157+
expect(channel.ready, throwsA(isA<WebSocketChannelException>()));
158158
expect(
159159
channel.stream.drain<void>(),
160160
throwsA(isA<WebSocketChannelException>()),
@@ -230,8 +230,7 @@ void main() {
230230
);
231231

232232
expect(channel.ready, throwsA(isA<TimeoutException>()));
233-
expect(channel.stream.drain<void>(),
234-
throwsA(isA<WebSocketChannelException>()));
233+
expect(channel.stream.drain<void>(), throwsA(anything));
235234
});
236235

237236
test('.custom client is passed through', () async {

pkgs/web_socket_channel/test/web_socket_adapter_web_socket_test.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,26 @@ void main() {
112112
expect(channel.closeReason, null);
113113
});
114114

115-
test('fromWebSocket', () async {
115+
test('constructor with WebSocket', () async {
116116
final webSocket = await WebSocket.connect(uri);
117-
final channel = WebSocketAdapterWebSocketChannel.fromWebSocket(webSocket);
117+
final channel = WebSocketAdapterWebSocketChannel(webSocket);
118+
119+
await expectLater(channel.ready, completes);
120+
channel.sink.add('This count says:');
121+
channel.sink.add([1, 2, 3]);
122+
channel.sink.add('And then:');
123+
channel.sink.add([4, 5, 6]);
124+
expect(await channel.stream.take(4).toList(), [
125+
'This count says:',
126+
[1, 2, 3],
127+
'And then:',
128+
[4, 5, 6]
129+
]);
130+
});
131+
132+
test('constructor with Future<WebSocket>', () async {
133+
final webSocketFuture = WebSocket.connect(uri);
134+
final channel = WebSocketAdapterWebSocketChannel(webSocketFuture);
118135

119136
await expectLater(channel.ready, completes);
120137
channel.sink.add('This count says:');

0 commit comments

Comments
 (0)