| // 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 = '']); | |
| @override | |
| String toString() { | |
| if (message.isEmpty) { | |
| return 'WebSocketException'; | |
| } else { | |
| return 'WebSocketException: $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']); | |
| @override | |
| String toString() { | |
| if (message.isEmpty) { | |
| return 'WebSocketConnectionClosed'; | |
| } else { | |
| return 'WebSocketConnectionClosed: $message'; | |
| } | |
| } | |
| } | |
| /// 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 occurred (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; | |
| } |