| Index: lib/html.dart
|
| diff --git a/lib/html.dart b/lib/html.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9d0b070150753daf8246285bd6946beec7747107
|
| --- /dev/null
|
| +++ b/lib/html.dart
|
| @@ -0,0 +1,146 @@
|
| +// Copyright (c) 2016, 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:html';
|
| +import 'dart:typed_data';
|
| +
|
| +import 'package:async/async.dart';
|
| +import 'package:stream_channel/stream_channel.dart';
|
| +
|
| +import 'src/channel.dart';
|
| +import 'src/exception.dart';
|
| +
|
| +/// A [WebSocketChannel] that communicates using a `dart:html` [WebSocket].
|
| +class HtmlWebSocketChannel extends StreamChannelMixin
|
| + implements WebSocketChannel {
|
| + /// The underlying `dart:html` [WebSocket].
|
| + final WebSocket _webSocket;
|
| +
|
| + String get protocol => _webSocket.protocol;
|
| +
|
| + int get closeCode => _closeCode;
|
| + int _closeCode;
|
| +
|
| + String get closeReason => _closeReason;
|
| + String _closeReason;
|
| +
|
| + /// The number of bytes of data that have been queued but not yet transmitted
|
| + /// to the network.
|
| + int get bufferedAmount => _webSocket.bufferedAmount;
|
| +
|
| + /// The close code set by the local user.
|
| + ///
|
| + /// To ensure proper ordering, this is stored until we get a done event on
|
| + /// [_controller.local.stream].
|
| + int _localCloseCode;
|
| +
|
| + /// The close reason set by the local user.
|
| + ///
|
| + /// To ensure proper ordering, this is stored until we get a done event on
|
| + /// [_controller.local.stream].
|
| + String _localCloseReason;
|
| +
|
| + Stream get stream => _controller.foreign.stream;
|
| + final _controller = new StreamChannelController(
|
| + sync: true, allowForeignErrors: false);
|
| +
|
| + WebSocketSink get sink => _sink;
|
| + WebSocketSink _sink;
|
| +
|
| + /// Creates a new WebSocket connection.
|
| + ///
|
| + /// Connects to [url] using [new WebSocket] and returns a channel that can be
|
| + /// used to communicate over the resulting socket. The [url] may be either a
|
| + /// [String] or a [Uri]. The [protocols] parameter is the same as for
|
| + /// [new WebSocket].
|
| + ///
|
| + /// The [binaryType] parameter controls what type is used for binary messages
|
| + /// received by this socket. It defaults to [BinaryType.list], which causes
|
| + /// binary messages to be delivered as [Uint8List]s. If it's
|
| + /// [BinaryType.blob], they're delivered as [Blob]s instead.
|
| + HtmlWebSocketChannel.connect(url, {Iterable<String> protocols,
|
| + BinaryType binaryType})
|
| + : this(new WebSocket(url.toString(), protocols)
|
| + ..binaryType = (binaryType ?? BinaryType.list).value);
|
| +
|
| + /// Creates a channel wrapping [webSocket].
|
| + HtmlWebSocketChannel(this._webSocket){
|
| + _sink = new _HtmlWebSocketSink(this);
|
| +
|
| + if (_webSocket.readyState == WebSocket.OPEN) {
|
| + _listen();
|
| + } else {
|
| + // The socket API guarantees that only a single open event will be
|
| + // emitted.
|
| + _webSocket.onOpen.first.then((_) {
|
| + _listen();
|
| + });
|
| + }
|
| +
|
| + // The socket API guarantees that only a single error event will be emitted,
|
| + // and that once it is no open or message events will be emitted.
|
| + _webSocket.onError.first.then((_) {
|
| + _controller.local.sink.addError(
|
| + new WebSocketChannelException("WebSocket connection failed."));
|
| + _controller.local.sink.close();
|
| + });
|
| +
|
| + _webSocket.onMessage.listen((event) {
|
| + var data = event.data;
|
| + if (data is ByteBuffer) data = data.asUint8List();
|
| + _controller.local.sink.add(data);
|
| + });
|
| +
|
| + // The socket API guarantees that only a single error event will be emitted,
|
| + // and that once it is no other events will be emitted.
|
| + _webSocket.onClose.first.then((event) {
|
| + _closeCode = event.code;
|
| + _closeReason = event.reason;
|
| + _controller.local.sink.close();
|
| + });
|
| + }
|
| +
|
| + /// Pipes user events to [_webSocket].
|
| + void _listen() {
|
| + _controller.local.stream.listen((message) => _webSocket.send(message),
|
| + onDone: () => _webSocket.close(_localCloseCode, _localCloseReason));
|
| + }
|
| +}
|
| +
|
| +/// A [WebSocketSink] that tracks the close code and reason passed to [close].
|
| +class _HtmlWebSocketSink extends DelegatingStreamSink implements WebSocketSink {
|
| + /// The channel to which this sink belongs.
|
| + final HtmlWebSocketChannel _channel;
|
| +
|
| + _HtmlWebSocketSink(HtmlWebSocketChannel channel)
|
| + : super(channel._controller.foreign.sink),
|
| + _channel = channel;
|
| +
|
| + Future close([int closeCode, String closeReason]) {
|
| + _channel._localCloseCode = closeCode;
|
| + _channel._localCloseReason = closeReason;
|
| + return super.close();
|
| + }
|
| +}
|
| +
|
| +/// An enum for choosing what type [HtmlWebSocketChannel] emits for binary
|
| +/// messages.
|
| +class BinaryType {
|
| + /// Tells the channel to emit binary messages as [Blob]s.
|
| + static const blob = const BinaryType._("blob", "blob");
|
| +
|
| + /// Tells the channel to emit binary messages as [Uint8List]s.
|
| + static const list = const BinaryType._("list", "arraybuffer");
|
| +
|
| + /// The name of the binary type, which matches its variable name.
|
| + final String name;
|
| +
|
| + /// The value as understood by the underlying [WebSocket] API.
|
| + final String value;
|
| +
|
| + const BinaryType._(this.name, this.value);
|
| +
|
| + String toString() => name;
|
| +}
|
|
|