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; |
+} |