| // 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:io' as io; | |
| import 'dart:typed_data'; | |
| import 'utils.dart'; | |
| import 'web_socket.dart'; | |
| /// A `dart-io`-based [WebSocket] implementation. | |
| /// | |
| /// Usable when targeting native platforms. | |
| class IOWebSocket implements WebSocket { | |
| final io.WebSocket _webSocket; | |
| final _events = StreamController<WebSocketEvent>(); | |
| /// 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<IOWebSocket> connect(Uri url, | |
| {Iterable<String>? 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 = | |
| await io.WebSocket.connect(url.toString(), protocols: protocols); | |
| } 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); | |
| } | |
| // Create an `IOWebSocket` from an existing `dart:io` `WebSocket`. | |
| factory IOWebSocket.fromWebSocket(io.WebSocket webSocket) => | |
| IOWebSocket._(webSocket); | |
| IOWebSocket._(this._webSocket) { | |
| _webSocket.listen( | |
| (event) { | |
| if (_events.isClosed) return; | |
| switch (event) { | |
| case String e: | |
| _events.add(TextDataReceived(e)); | |
| case List<int> e: | |
| _events.add(BinaryDataReceived(Uint8List.fromList(e))); | |
| } | |
| }, | |
| onError: (Object e, StackTrace st) { | |
| if (_events.isClosed) return; | |
| final wse = switch (e) { | |
| io.WebSocketException(message: final message) => | |
| WebSocketException(message), | |
| _ => WebSocketException(e.toString()), | |
| }; | |
| _events.addError(wse, st); | |
| }, | |
| onDone: () { | |
| if (_events.isClosed) return; | |
| _events | |
| ..add( | |
| CloseReceived(_webSocket.closeCode, _webSocket.closeReason ?? '')) | |
| ..close(); | |
| }, | |
| ); | |
| } | |
| @override | |
| void sendBytes(Uint8List b) { | |
| if (_events.isClosed) { | |
| throw WebSocketConnectionClosed(); | |
| } | |
| _webSocket.add(b); | |
| } | |
| @override | |
| void sendText(String s) { | |
| if (_events.isClosed) { | |
| throw WebSocketConnectionClosed(); | |
| } | |
| _webSocket.add(s); | |
| } | |
| @override | |
| Future<void> close([int? code, String? reason]) async { | |
| if (_events.isClosed) { | |
| throw WebSocketConnectionClosed(); | |
| } | |
| checkCloseCode(code); | |
| checkCloseReason(reason); | |
| unawaited(_events.close()); | |
| try { | |
| await _webSocket.close(code, reason); | |
| } on io.WebSocketException catch (e) { | |
| throw WebSocketException(e.message); | |
| } | |
| } | |
| @override | |
| Stream<WebSocketEvent> get events => _events.stream; | |
| @override | |
| String get protocol => _webSocket.protocol ?? ''; | |
| } | |
| const connect = IOWebSocket.connect; |