From 19738ab1e0904d3ce1577ad11380fae4e09d3030 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 4 Mar 2024 16:18:52 -0800 Subject: [PATCH 1/8] Factory --- pkgs/web_socket/README.md | 3 +-- pkgs/web_socket/lib/src/browser_web_socket.dart | 12 ++++++++++-- pkgs/web_socket/lib/src/connect_stub.dart | 5 +++++ pkgs/web_socket/lib/src/io_web_socket.dart | 11 +++++++++-- pkgs/web_socket/lib/src/web_socket.dart | 12 ++++++++++-- .../test/browser_web_socket_conformance_test.dart | 2 +- .../test/io_web_socket_conformance_test.dart | 2 +- pkgs/web_socket/test/websocket_test.dart | 10 ++++++++++ 8 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 pkgs/web_socket/lib/src/connect_stub.dart create mode 100644 pkgs/web_socket/test/websocket_test.dart diff --git a/pkgs/web_socket/README.md b/pkgs/web_socket/README.md index 3e9c2d0146..786d239891 100644 --- a/pkgs/web_socket/README.md +++ b/pkgs/web_socket/README.md @@ -7,12 +7,11 @@ implementations. ## Using ```dart -import 'package:web_socket/io_web_socket.dart'; import 'package:web_socket/web_socket.dart'; void main() async { final socket = - await IOWebSocket.connect(Uri.parse('wss://ws.postman-echo.com/raw')); + await WebSocket.connect(Uri.parse('wss://ws.postman-echo.com/raw')); socket.events.listen((e) async { switch (e) { diff --git a/pkgs/web_socket/lib/src/browser_web_socket.dart b/pkgs/web_socket/lib/src/browser_web_socket.dart index eceb86c02c..a98afe9262 100644 --- a/pkgs/web_socket/lib/src/browser_web_socket.dart +++ b/pkgs/web_socket/lib/src/browser_web_socket.dart @@ -18,8 +18,11 @@ class BrowserWebSocket implements WebSocket { final web.WebSocket _webSocket; final _events = StreamController(); - static Future connect(Uri url) async { - final webSocket = web.WebSocket(url.toString())..binaryType = 'arraybuffer'; + static Future connect(Uri url, + {Iterable? protocols}) async { + final webSocket = web.WebSocket(url.toString(), + protocols?.map((e) => e.toJS).toList().toJS ?? JSArray()) + ..binaryType = 'arraybuffer'; final browserSocket = BrowserWebSocket._(webSocket); final webSocketConnected = Completer(); @@ -125,4 +128,9 @@ class BrowserWebSocket implements WebSocket { @override Stream get events => _events.stream; + + @override + String get protocol => _webSocket.protocol; } + +const connect = BrowserWebSocket.connect; diff --git a/pkgs/web_socket/lib/src/connect_stub.dart b/pkgs/web_socket/lib/src/connect_stub.dart new file mode 100644 index 0000000000..828a09dc63 --- /dev/null +++ b/pkgs/web_socket/lib/src/connect_stub.dart @@ -0,0 +1,5 @@ +import '../web_socket.dart'; + +Future connect(Uri url, {Iterable? protocols}) { + throw UnsupportedError('Cannot connect without dart:js_interop or dart:io.'); +} diff --git a/pkgs/web_socket/lib/src/io_web_socket.dart b/pkgs/web_socket/lib/src/io_web_socket.dart index 3b17ccdf58..1fbd43c64e 100644 --- a/pkgs/web_socket/lib/src/io_web_socket.dart +++ b/pkgs/web_socket/lib/src/io_web_socket.dart @@ -16,9 +16,11 @@ class IOWebSocket implements WebSocket { final io.WebSocket _webSocket; final _events = StreamController(); - static Future connect(Uri uri) async { + static Future connect(Uri url, + {Iterable? protocols}) async { try { - final webSocket = await io.WebSocket.connect(uri.toString()); + final webSocket = + await io.WebSocket.connect(url.toString(), protocols: protocols); return IOWebSocket._(webSocket); } on io.WebSocketException catch (e) { throw WebSocketException(e.message); @@ -89,4 +91,9 @@ class IOWebSocket implements WebSocket { @override Stream get events => _events.stream; + + @override + String get protocol => _webSocket.protocol ?? ''; } + +const connect = IOWebSocket.connect; diff --git a/pkgs/web_socket/lib/src/web_socket.dart b/pkgs/web_socket/lib/src/web_socket.dart index dfd3486f00..e1b8ce885c 100644 --- a/pkgs/web_socket/lib/src/web_socket.dart +++ b/pkgs/web_socket/lib/src/web_socket.dart @@ -4,6 +4,10 @@ import 'dart:typed_data'; +import 'connect_stub.dart' + if (dart.library.js_interop) 'browser_web_socket.dart' + if (dart.library.io) 'io_web_socket.dart' as connector; + /// An event received from the peer through the [WebSocket]. sealed class WebSocketEvent {} @@ -90,12 +94,11 @@ class WebSocketConnectionClosed extends WebSocketException { /// The interface for WebSocket connections. /// /// ```dart -/// import 'package:web_socket/io_web_socket.dart'; /// import 'package:web_socket/src/web_socket.dart'; /// /// void main() async { /// final socket = -/// await IOWebSocket.connect(Uri.parse('wss://ws.postman-echo.com/raw')); +/// await WebSocket.connect(Uri.parse('wss://ws.postman-echo.com/raw')); /// /// socket.events.listen((e) async { /// switch (e) { @@ -112,6 +115,9 @@ class WebSocketConnectionClosed extends WebSocketException { /// socket.sendText('Hello Dart WebSockets! 🎉'); /// } abstract interface class WebSocket { + static Future connect(Uri url, {Iterable? protocols}) => + connector.connect(url, protocols: protocols); + /// Sends text data to the connected peer. /// /// Throws [WebSocketConnectionClosed] if the [WebSocket] is @@ -163,4 +169,6 @@ abstract interface class WebSocket { /// /// Errors will never appear in this [Stream]. Stream get events; + + String get protocol; } diff --git a/pkgs/web_socket/test/browser_web_socket_conformance_test.dart b/pkgs/web_socket/test/browser_web_socket_conformance_test.dart index caddff137c..2105a45508 100644 --- a/pkgs/web_socket/test/browser_web_socket_conformance_test.dart +++ b/pkgs/web_socket/test/browser_web_socket_conformance_test.dart @@ -10,5 +10,5 @@ import 'package:web_socket/browser_web_socket.dart'; import 'package:web_socket_conformance_tests/web_socket_conformance_tests.dart'; void main() { - testAll((uri, {protocols}) => BrowserWebSocket.connect(uri)); + testAll(BrowserWebSocket.connect); } diff --git a/pkgs/web_socket/test/io_web_socket_conformance_test.dart b/pkgs/web_socket/test/io_web_socket_conformance_test.dart index 9b3728ddd2..cc313a6a0d 100644 --- a/pkgs/web_socket/test/io_web_socket_conformance_test.dart +++ b/pkgs/web_socket/test/io_web_socket_conformance_test.dart @@ -10,5 +10,5 @@ import 'package:web_socket/io_web_socket.dart'; import 'package:web_socket_conformance_tests/web_socket_conformance_tests.dart'; void main() { - testAll((uri, {protocols}) => IOWebSocket.connect(uri)); + testAll(IOWebSocket.connect); } diff --git a/pkgs/web_socket/test/websocket_test.dart b/pkgs/web_socket/test/websocket_test.dart new file mode 100644 index 0000000000..859e4fe636 --- /dev/null +++ b/pkgs/web_socket/test/websocket_test.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:web_socket/web_socket.dart'; +import 'package:web_socket_conformance_tests/web_socket_conformance_tests.dart'; + +void main() { + testAll(WebSocket.connect); +} From a94ca1fd7decc68ee6e18177702229ec358672d7 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Tue, 5 Mar 2024 11:50:44 -0800 Subject: [PATCH 2/8] Protocol support --- pkgs/web_socket/lib/src/io_web_socket.dart | 16 ++++- pkgs/web_socket/lib/src/web_socket.dart | 13 ++++ .../example/client_test.dart | 3 + .../lib/src/protocol_server.dart | 46 +++++++++++++ .../lib/src/protocol_server_vm.dart | 12 ++++ .../lib/src/protocol_server_web.dart | 9 +++ .../lib/src/protocol_tests.dart | 66 +++++++++++++++++++ .../lib/web_socket_conformance_tests.dart | 2 + 8 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart create mode 100644 pkgs/web_socket_conformance_tests/lib/src/protocol_server_vm.dart create mode 100644 pkgs/web_socket_conformance_tests/lib/src/protocol_server_web.dart create mode 100644 pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart diff --git a/pkgs/web_socket/lib/src/io_web_socket.dart b/pkgs/web_socket/lib/src/io_web_socket.dart index 1fbd43c64e..ad345285d4 100644 --- a/pkgs/web_socket/lib/src/io_web_socket.dart +++ b/pkgs/web_socket/lib/src/io_web_socket.dart @@ -6,8 +6,8 @@ import 'dart:async'; import 'dart:io' as io; import 'dart:typed_data'; -import '../web_socket.dart'; import 'utils.dart'; +import 'web_socket.dart'; /// A `dart-io`-based [WebSocket] implementation. /// @@ -18,13 +18,23 @@ class IOWebSocket implements WebSocket { static Future connect(Uri url, {Iterable? protocols}) async { + final io.WebSocket webSocket; try { - final webSocket = + webSocket = await io.WebSocket.connect(url.toString(), protocols: protocols); - return IOWebSocket._(webSocket); } on io.WebSocketException catch (e) { throw WebSocketException(e.message); } + + if (webSocket.protocol != null && + !(protocols ?? []).contains(webSocket.protocol)) { + // dart:io WebSocket does not correctly validate the returned protocol. + // See https://github.com/dart-lang/sdk/issues/55106 + await webSocket.close(1002); // protocol error + throw WebSocketException( + 'unexpected protocol selected by peer: ${webSocket.protocol}'); + } + return IOWebSocket._(webSocket); } IOWebSocket._(this._webSocket) { diff --git a/pkgs/web_socket/lib/src/web_socket.dart b/pkgs/web_socket/lib/src/web_socket.dart index e1b8ce885c..560a5eb04e 100644 --- a/pkgs/web_socket/lib/src/web_socket.dart +++ b/pkgs/web_socket/lib/src/web_socket.dart @@ -115,6 +115,13 @@ class WebSocketConnectionClosed extends WebSocketException { /// socket.sendText('Hello Dart WebSockets! 🎉'); /// } abstract interface class WebSocket { + /// Create a new WebSocket connection. + /// + /// The URL supplied in [url] must use the scheme ws or wss. + /// + /// If provided, the [protocols] argument indicates that subprotocols that + /// the peer is able to select. See + /// [RFC-6455 1.9](https://datatracker.ietf.org/doc/html/rfc6455#section-1.9). static Future connect(Uri url, {Iterable? protocols}) => connector.connect(url, protocols: protocols); @@ -170,5 +177,11 @@ abstract interface class WebSocket { /// Errors will never appear in this [Stream]. Stream get events; + /// The WebSocket subprotocol negotiated with the peer. + /// + /// Will be the empty string if no subprotocol was negotiated. + /// + /// See + /// [RFC-6455 1.9](https://datatracker.ietf.org/doc/html/rfc6455#section-1.9). String get protocol; } diff --git a/pkgs/web_socket_conformance_tests/example/client_test.dart b/pkgs/web_socket_conformance_tests/example/client_test.dart index ec3d01c17a..d08dad94c1 100644 --- a/pkgs/web_socket_conformance_tests/example/client_test.dart +++ b/pkgs/web_socket_conformance_tests/example/client_test.dart @@ -20,6 +20,9 @@ class MyWebSocketImplementation implements WebSocket { @override void sendText(String s) => throw UnimplementedError(); + + @override + String get protocol => throw UnimplementedError(); } void main() { diff --git a/pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart b/pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart new file mode 100644 index 0000000000..cc3c3aa00f --- /dev/null +++ b/pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart @@ -0,0 +1,46 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:crypto/crypto.dart'; +import 'package:stream_channel/stream_channel.dart'; + +const _webSocketGuid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + +/// Starts an WebSocket server that responds with a scripted subprotocol. +void hybridMain(StreamChannel channel) async { + late final HttpServer server; + server = (await HttpServer.bind('localhost', 0)) + ..listen((request) async { + final serverProtocol = request.requestedUri.queryParameters['protocol']; + var key = request.headers.value('Sec-WebSocket-Key'); + var digest = sha1.convert('$key$_webSocketGuid'.codeUnits); + var accept = base64.encode(digest.bytes); + channel.sink.add(request.headers['Sec-WebSocket-Protocol']); + request.response + ..statusCode = HttpStatus.switchingProtocols + ..headers.add(HttpHeaders.connectionHeader, 'Upgrade') + ..headers.add(HttpHeaders.upgradeHeader, 'websocket') + ..headers.add('Sec-WebSocket-Accept', accept); + if (serverProtocol != null) { + request.response.headers.add('Sec-WebSocket-Protocol', serverProtocol); + } + request.response.contentLength = 0; + final socket = await request.response.detachSocket(); + final webSocket = WebSocket.fromUpgradedSocket(socket, + protocol: serverProtocol, serverSide: true); + webSocket.listen((e) async { + webSocket.add(e); + await webSocket.close(); + }); + }); + + channel.sink.add(server.port); + + await channel + .stream.first; // Any writes indicates that the server should exit. + unawaited(server.close()); +} diff --git a/pkgs/web_socket_conformance_tests/lib/src/protocol_server_vm.dart b/pkgs/web_socket_conformance_tests/lib/src/protocol_server_vm.dart new file mode 100644 index 0000000000..a31da9ec1e --- /dev/null +++ b/pkgs/web_socket_conformance_tests/lib/src/protocol_server_vm.dart @@ -0,0 +1,12 @@ +// Generated by generate_server_wrappers.dart. Do not edit. + +import 'package:stream_channel/stream_channel.dart'; + +import 'protocol_server.dart'; + +/// Starts the redirect test HTTP server in the same process. +Future> startServer() async { + final controller = StreamChannelController(sync: true); + hybridMain(controller.foreign); + return controller.local; +} diff --git a/pkgs/web_socket_conformance_tests/lib/src/protocol_server_web.dart b/pkgs/web_socket_conformance_tests/lib/src/protocol_server_web.dart new file mode 100644 index 0000000000..a752ed7ac2 --- /dev/null +++ b/pkgs/web_socket_conformance_tests/lib/src/protocol_server_web.dart @@ -0,0 +1,9 @@ +// Generated by generate_server_wrappers.dart. Do not edit. + +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/test.dart'; + +/// Starts the redirect test HTTP server out-of-process. +Future> startServer() async => spawnHybridUri(Uri( + scheme: 'package', + path: 'web_socket_conformance_tests/src/protocol_server.dart')); diff --git a/pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart new file mode 100644 index 0000000000..332c591bd1 --- /dev/null +++ b/pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart @@ -0,0 +1,66 @@ +import 'package:async/async.dart'; +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/test.dart'; +import 'package:web_socket/web_socket.dart'; + +import 'protocol_server_vm.dart' + if (dart.library.html) 'protocol_server_web.dart'; + +/// Tests that the [WebSocket] can correctly negotiate a subprotocol with the +/// peer. +/// +/// See +/// [RFC-6455 1.9](https://datatracker.ietf.org/doc/html/rfc6455#section-1.9). +void testProtocols( + Future Function(Uri uri, {Iterable? protocols}) + channelFactory) { + group('protocols', () { + late Uri uri; + late StreamChannel httpServerChannel; + late StreamQueue httpServerQueue; + + setUp(() async { + httpServerChannel = await startServer(); + httpServerQueue = StreamQueue(httpServerChannel.stream); + uri = Uri.parse('ws://localhost:${await httpServerQueue.next}'); + }); + tearDown(() => httpServerChannel.sink.add(null)); + + test('no protocol', () async { + final socket = await channelFactory(uri); + + expect(await httpServerQueue.next, null); + expect(socket.protocol, ''); + socket.sendText('Hello World!'); + }); + + test('single protocol', () async { + final socket = await channelFactory( + uri.replace(queryParameters: {'protocol': 'chat.example.com'}), + protocols: ['chat.example.com']); + + expect(await httpServerQueue.next, ['chat.example.com']); + expect(socket.protocol, 'chat.example.com'); + socket.sendText('Hello World!'); + }); + + test('mutiple protocols', () async { + final socket = await channelFactory( + uri.replace(queryParameters: {'protocol': 'text.example.com'}), + protocols: ['chat.example.com', 'text.example.com']); + + expect( + await httpServerQueue.next, ['chat.example.com, text.example.com']); + expect(socket.protocol, 'text.example.com'); + socket.sendText('Hello World!'); + }); + + test('protocol mismatch', () async { + await expectLater( + () => channelFactory( + uri.replace(queryParameters: {'protocol': 'example.example.com'}), + protocols: ['chat.example.com']), + throwsA(isA())); + }); + }); +} diff --git a/pkgs/web_socket_conformance_tests/lib/web_socket_conformance_tests.dart b/pkgs/web_socket_conformance_tests/lib/web_socket_conformance_tests.dart index 248fc3870a..f0586e0e47 100644 --- a/pkgs/web_socket_conformance_tests/lib/web_socket_conformance_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/web_socket_conformance_tests.dart @@ -9,6 +9,7 @@ import 'src/disconnect_after_upgrade_tests.dart'; import 'src/no_upgrade_tests.dart'; import 'src/payload_transfer_tests.dart'; import 'src/peer_protocol_errors_tests.dart'; +import 'src/protocol_tests.dart'; /// Runs the entire test suite against the given [WebSocket]. void testAll( @@ -20,4 +21,5 @@ void testAll( testNoUpgrade(webSocketFactory); testPayloadTransfer(webSocketFactory); testPeerProtocolErrors(webSocketFactory); + testProtocols(webSocketFactory); } From 693c35c6f921d5dd944c9c6cd92476bc07c8b7dc Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Tue, 5 Mar 2024 12:06:47 -0800 Subject: [PATCH 3/8] Docs --- pkgs/web_socket/lib/src/browser_web_socket.dart | 7 +++++++ pkgs/web_socket/lib/src/io_web_socket.dart | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/pkgs/web_socket/lib/src/browser_web_socket.dart b/pkgs/web_socket/lib/src/browser_web_socket.dart index a98afe9262..7fb61a9e33 100644 --- a/pkgs/web_socket/lib/src/browser_web_socket.dart +++ b/pkgs/web_socket/lib/src/browser_web_socket.dart @@ -18,6 +18,13 @@ class BrowserWebSocket implements WebSocket { final web.WebSocket _webSocket; final _events = StreamController(); + /// Create a new WebSocket connection using the JavaScript WebSocket API. + /// + /// The URL supplied in [url] must use the scheme ws or wss. + /// + /// If provided, the [protocols] argument indicates that subprotocols that + /// the peer is able to select. See + /// [RFC-6455 1.9](https://datatracker.ietf.org/doc/html/rfc6455#section-1.9). static Future connect(Uri url, {Iterable? protocols}) async { final webSocket = web.WebSocket(url.toString(), diff --git a/pkgs/web_socket/lib/src/io_web_socket.dart b/pkgs/web_socket/lib/src/io_web_socket.dart index ad345285d4..3d7da9f9fb 100644 --- a/pkgs/web_socket/lib/src/io_web_socket.dart +++ b/pkgs/web_socket/lib/src/io_web_socket.dart @@ -16,6 +16,13 @@ class IOWebSocket implements WebSocket { final io.WebSocket _webSocket; final _events = StreamController(); + /// Create a new WebSocket connection using dart:io WebSocket. + /// + /// The URL supplied in [url] must use the scheme ws or wss. + /// + /// If provided, the [protocols] argument indicates that subprotocols that + /// the peer is able to select. See + /// [RFC-6455 1.9](https://datatracker.ietf.org/doc/html/rfc6455#section-1.9). static Future connect(Uri url, {Iterable? protocols}) async { final io.WebSocket webSocket; From 49880a38374fcdadd9a355a2a33dff185a051c88 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Tue, 5 Mar 2024 14:50:43 -0800 Subject: [PATCH 4/8] Update CHANGELOG.md --- pkgs/web_socket/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/web_socket/CHANGELOG.md b/pkgs/web_socket/CHANGELOG.md index 3d138c0f6e..3bd731a51b 100644 --- a/pkgs/web_socket/CHANGELOG.md +++ b/pkgs/web_socket/CHANGELOG.md @@ -1,3 +1,3 @@ ## 0.1.0-wip -- Abstract interface definition. +- Basic functionality in place. From 83763220236bac12992da801944f715aae5dfc09 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Tue, 5 Mar 2024 15:07:14 -0800 Subject: [PATCH 5/8] Connect URI --- pkgs/web_socket/lib/src/browser_web_socket.dart | 4 ++++ .../lib/src/connect_uri_tests.dart | 14 ++++++++++++++ .../lib/web_socket_conformance_tests.dart | 2 ++ 3 files changed, 20 insertions(+) create mode 100644 pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart diff --git a/pkgs/web_socket/lib/src/browser_web_socket.dart b/pkgs/web_socket/lib/src/browser_web_socket.dart index 7fb61a9e33..694ed0ccfd 100644 --- a/pkgs/web_socket/lib/src/browser_web_socket.dart +++ b/pkgs/web_socket/lib/src/browser_web_socket.dart @@ -27,6 +27,10 @@ class BrowserWebSocket implements WebSocket { /// [RFC-6455 1.9](https://datatracker.ietf.org/doc/html/rfc6455#section-1.9). static Future connect(Uri url, {Iterable? protocols}) async { + if (!url.isScheme('ws') && !url.isScheme('wss')) { + throw WebSocketException("Unsupported URL scheme '${url.scheme}'"); + } + final webSocket = web.WebSocket(url.toString(), protocols?.map((e) => e.toJS).toList().toJS ?? JSArray()) ..binaryType = 'arraybuffer'; diff --git a/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart new file mode 100644 index 0000000000..f909302acd --- /dev/null +++ b/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart @@ -0,0 +1,14 @@ +import 'package:test/test.dart'; +import 'package:web_socket/web_socket.dart'; + +/// Tests that the [WebSocket] rejects invalid connection URIs. +void testConnectUri( + Future Function(Uri uri, {Iterable? protocols}) + channelFactory) { + group('connect uri', () { + test('no protocol', () async { + await expectLater(() => channelFactory(Uri.https('www.example.com', '/')), + throwsA(isA())); + }); + }); +} diff --git a/pkgs/web_socket_conformance_tests/lib/web_socket_conformance_tests.dart b/pkgs/web_socket_conformance_tests/lib/web_socket_conformance_tests.dart index f0586e0e47..9e6e011628 100644 --- a/pkgs/web_socket_conformance_tests/lib/web_socket_conformance_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/web_socket_conformance_tests.dart @@ -5,6 +5,7 @@ import 'package:web_socket/web_socket.dart'; import 'src/close_local_tests.dart'; import 'src/close_remote_tests.dart'; +import 'src/connect_uri_tests.dart'; import 'src/disconnect_after_upgrade_tests.dart'; import 'src/no_upgrade_tests.dart'; import 'src/payload_transfer_tests.dart'; @@ -17,6 +18,7 @@ void testAll( webSocketFactory) { testCloseLocal(webSocketFactory); testCloseRemote(webSocketFactory); + testConnectUri(webSocketFactory); testDisconnectAfterUpgrade(webSocketFactory); testNoUpgrade(webSocketFactory); testPayloadTransfer(webSocketFactory); From 0b7b8db1626222b381a5f159a1d852d99bdd0489 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Tue, 5 Mar 2024 15:10:40 -0800 Subject: [PATCH 6/8] Add copyright --- .../lib/src/connect_uri_tests.dart | 4 ++++ pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart index f909302acd..5550dc5d83 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'package:test/test.dart'; import 'package:web_socket/web_socket.dart'; diff --git a/pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart index 332c591bd1..4af977fb7c 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/protocol_tests.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'package:async/async.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; From 8bc9c3aa47afeb0d07b66deac2758818a318e2a8 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 6 Mar 2024 17:54:03 -0800 Subject: [PATCH 7/8] Update pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart Co-authored-by: Nate Bosch --- pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart b/pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart index cc3c3aa00f..c0df5b6ea4 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/protocol_server.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; + import 'package:crypto/crypto.dart'; import 'package:stream_channel/stream_channel.dart'; From 542ed120e970d1b9c98ae9538ea9a94a10140843 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 6 Mar 2024 18:01:56 -0800 Subject: [PATCH 8/8] Change to ArgumentError --- pkgs/web_socket/lib/src/browser_web_socket.dart | 3 ++- pkgs/web_socket/lib/src/io_web_socket.dart | 5 +++++ .../lib/src/connect_uri_tests.dart | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkgs/web_socket/lib/src/browser_web_socket.dart b/pkgs/web_socket/lib/src/browser_web_socket.dart index 694ed0ccfd..80135fdc3e 100644 --- a/pkgs/web_socket/lib/src/browser_web_socket.dart +++ b/pkgs/web_socket/lib/src/browser_web_socket.dart @@ -28,7 +28,8 @@ class BrowserWebSocket implements WebSocket { static Future connect(Uri url, {Iterable? protocols}) async { if (!url.isScheme('ws') && !url.isScheme('wss')) { - throw WebSocketException("Unsupported URL scheme '${url.scheme}'"); + throw ArgumentError.value( + url, 'url', 'only ws: and wss: schemes are supported'); } final webSocket = web.WebSocket(url.toString(), diff --git a/pkgs/web_socket/lib/src/io_web_socket.dart b/pkgs/web_socket/lib/src/io_web_socket.dart index 3d7da9f9fb..d44a33ecfa 100644 --- a/pkgs/web_socket/lib/src/io_web_socket.dart +++ b/pkgs/web_socket/lib/src/io_web_socket.dart @@ -25,6 +25,11 @@ class IOWebSocket implements WebSocket { /// [RFC-6455 1.9](https://datatracker.ietf.org/doc/html/rfc6455#section-1.9). static Future connect(Uri url, {Iterable? protocols}) async { + if (!url.isScheme('ws') && !url.isScheme('wss')) { + throw ArgumentError.value( + url, 'url', 'only ws: and wss: schemes are supported'); + } + final io.WebSocket webSocket; try { webSocket = diff --git a/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart b/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart index 5550dc5d83..0caa9e6f8c 100644 --- a/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart +++ b/pkgs/web_socket_conformance_tests/lib/src/connect_uri_tests.dart @@ -12,7 +12,7 @@ void testConnectUri( group('connect uri', () { test('no protocol', () async { await expectLater(() => channelFactory(Uri.https('www.example.com', '/')), - throwsA(isA())); + throwsA(isA())); }); }); }