blob: 5e9d7ccd805ad3aeb8f2872386a0c9aada31a533 [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: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 {}
/// Text data received from the peer through the [WebSocket].
///
/// See [WebSocket.events].
final class TextDataReceived extends WebSocketEvent {
final String text;
TextDataReceived(this.text);
@override
bool operator ==(Object other) =>
other is TextDataReceived && other.text == text;
@override
int get hashCode => text.hashCode;
}
/// Binary data received from the peer through the [WebSocket].
///
/// See [WebSocket.events].
final class BinaryDataReceived extends WebSocketEvent {
final Uint8List data;
BinaryDataReceived(this.data);
@override
bool operator ==(Object other) {
if (other is BinaryDataReceived && other.data.length == data.length) {
for (var i = 0; i < data.length; ++i) {
if (other.data[i] != data[i]) return false;
}
return true;
}
return false;
}
@override
int get hashCode => data.hashCode;
@override
String toString() => 'BinaryDataReceived($data)';
}
/// A close notification (Close frame) received from the peer through the
/// [WebSocket] or a failure indication.
///
/// See [WebSocket.events].
final class CloseReceived extends WebSocketEvent {
/// A numerical code indicating the reason why the WebSocket was closed.
///
/// See [RFC-6455 7.4](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4)
/// for guidance on how to interpret these codes.
final int? code;
/// A textual explanation of the reason why the WebSocket was closed.
///
/// Will be empty if the peer did not specify a reason.
final String reason;
CloseReceived([this.code, this.reason = '']);
@override
bool operator ==(Object other) =>
other is CloseReceived && other.code == code && other.reason == reason;
@override
int get hashCode => [code, reason].hashCode;
@override
String toString() => 'CloseReceived($code, $reason)';
}
class WebSocketException implements Exception {
final String message;
WebSocketException([this.message = '']);
}
/// Thrown if [WebSocket.sendText], [WebSocket.sendBytes], or
/// [WebSocket.close] is called when the [WebSocket] is closed.
class WebSocketConnectionClosed extends WebSocketException {
WebSocketConnectionClosed([super.message = 'Connection Closed']);
}
/// The interface for WebSocket connections.
///
/// ```dart
/// import 'package:web_socket/src/web_socket.dart';
///
/// void main() async {
/// final socket =
/// await WebSocket.connect(Uri.parse('wss://ws.postman-echo.com/raw'));
///
/// socket.events.listen((e) async {
/// switch (e) {
/// case TextDataReceived(text: final text):
/// print('Received Text: $text');
/// await socket.close();
/// case BinaryDataReceived(data: final data):
/// print('Received Binary: $data');
/// case CloseReceived(code: final code, reason: final reason):
/// print('Connection to server closed: $code [$reason]');
/// }
/// });
///
/// 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<WebSocket> connect(Uri url, {Iterable<String>? protocols}) =>
connector.connect(url, protocols: protocols);
/// Sends text data to the connected peer.
///
/// Throws [WebSocketConnectionClosed] if the [WebSocket] is
/// closed (either through [close] or by the peer).
///
/// Data sent through [sendText] will be silently discarded if the peer is
/// disconnected but the disconnect has not yet been detected.
void sendText(String s);
/// Sends binary data to the connected peer.
///
/// Throws [WebSocketConnectionClosed] if the [WebSocket] is
/// closed (either through [close] or by the peer).
///
/// Data sent through [sendBytes] will be silently discarded if the peer is
/// disconnected but the disconnect has not yet been detected.
void sendBytes(Uint8List b);
/// Closes the WebSocket connection and the [events] `Stream`.
///
/// Sends a Close frame to the peer. If the optional [code] and [reason]
/// arguments are given, they will be included in the Close frame. If no
/// [code] is set then the peer will see a 1005 status code. If no [reason]
/// is set then the peer will not receive a reason string.
///
/// Throws an [ArgumentError] if [code] is not 1000 or in the range 3000-4999.
///
/// Throws an [ArgumentError] if [reason] is longer than 123 bytes when
/// encoded as UTF-8
///
/// Throws [WebSocketConnectionClosed] if the connection is already
/// closed (including by the peer).
Future<void> close([int? code, String? reason]);
/// A [Stream] of [WebSocketEvent] received from the peer.
///
/// Data received by the peer will be delivered as a [TextDataReceived] or
/// [BinaryDataReceived].
///
/// 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. [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).
///
/// Errors will never appear in this [Stream].
Stream<WebSocketEvent> 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;
}