blob: 7ea084bec3863b7f697fa5ff922dc033fbe0bf22 [file] [log] [blame]
// 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;