OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 import 'dart:async'; |
| 6 import 'dart:html'; |
| 7 import 'dart:typed_data'; |
| 8 |
| 9 import 'package:async/async.dart'; |
| 10 import 'package:stream_channel/stream_channel.dart'; |
| 11 |
| 12 import 'src/channel.dart'; |
| 13 import 'src/exception.dart'; |
| 14 |
| 15 /// A [WebSocketChannel] that communicates using a `dart:html` [WebSocket]. |
| 16 class HtmlWebSocketChannel extends StreamChannelMixin |
| 17 implements WebSocketChannel { |
| 18 /// The underlying `dart:html` [WebSocket]. |
| 19 final WebSocket _webSocket; |
| 20 |
| 21 String get protocol => _webSocket.protocol; |
| 22 |
| 23 int get closeCode => _closeCode; |
| 24 int _closeCode; |
| 25 |
| 26 String get closeReason => _closeReason; |
| 27 String _closeReason; |
| 28 |
| 29 /// The number of bytes of data that have been queued but not yet transmitted |
| 30 /// to the network. |
| 31 int get bufferedAmount => _webSocket.bufferedAmount; |
| 32 |
| 33 /// The close code set by the local user. |
| 34 /// |
| 35 /// To ensure proper ordering, this is stored until we get a done event on |
| 36 /// [_controller.local.stream]. |
| 37 int _localCloseCode; |
| 38 |
| 39 /// The close reason set by the local user. |
| 40 /// |
| 41 /// To ensure proper ordering, this is stored until we get a done event on |
| 42 /// [_controller.local.stream]. |
| 43 String _localCloseReason; |
| 44 |
| 45 Stream get stream => _controller.foreign.stream; |
| 46 final _controller = new StreamChannelController( |
| 47 sync: true, allowForeignErrors: false); |
| 48 |
| 49 WebSocketSink get sink => _sink; |
| 50 WebSocketSink _sink; |
| 51 |
| 52 /// Creates a new WebSocket connection. |
| 53 /// |
| 54 /// Connects to [url] using [new WebSocket] and returns a channel that can be |
| 55 /// used to communicate over the resulting socket. The [url] may be either a |
| 56 /// [String] or a [Uri]. The [protocols] parameter is the same as for |
| 57 /// [new WebSocket]. |
| 58 /// |
| 59 /// The [binaryType] parameter controls what type is used for binary messages |
| 60 /// received by this socket. It defaults to [BinaryType.list], which causes |
| 61 /// binary messages to be delivered as [Uint8List]s. If it's |
| 62 /// [BinaryType.blob], they're delivered as [Blob]s instead. |
| 63 HtmlWebSocketChannel.connect(url, {Iterable<String> protocols, |
| 64 BinaryType binaryType}) |
| 65 : this(new WebSocket(url.toString(), protocols) |
| 66 ..binaryType = (binaryType ?? BinaryType.list).value); |
| 67 |
| 68 /// Creates a channel wrapping [webSocket]. |
| 69 HtmlWebSocketChannel(this._webSocket){ |
| 70 _sink = new _HtmlWebSocketSink(this); |
| 71 |
| 72 if (_webSocket.readyState == WebSocket.OPEN) { |
| 73 _listen(); |
| 74 } else { |
| 75 // The socket API guarantees that only a single open event will be |
| 76 // emitted. |
| 77 _webSocket.onOpen.first.then((_) { |
| 78 _listen(); |
| 79 }); |
| 80 } |
| 81 |
| 82 // The socket API guarantees that only a single error event will be emitted, |
| 83 // and that once it is no open or message events will be emitted. |
| 84 _webSocket.onError.first.then((_) { |
| 85 _controller.local.sink.addError( |
| 86 new WebSocketChannelException("WebSocket connection failed.")); |
| 87 _controller.local.sink.close(); |
| 88 }); |
| 89 |
| 90 _webSocket.onMessage.listen((event) { |
| 91 var data = event.data; |
| 92 if (data is ByteBuffer) data = data.asUint8List(); |
| 93 _controller.local.sink.add(data); |
| 94 }); |
| 95 |
| 96 // The socket API guarantees that only a single error event will be emitted, |
| 97 // and that once it is no other events will be emitted. |
| 98 _webSocket.onClose.first.then((event) { |
| 99 _closeCode = event.code; |
| 100 _closeReason = event.reason; |
| 101 _controller.local.sink.close(); |
| 102 }); |
| 103 } |
| 104 |
| 105 /// Pipes user events to [_webSocket]. |
| 106 void _listen() { |
| 107 _controller.local.stream.listen((message) => _webSocket.send(message), |
| 108 onDone: () => _webSocket.close(_localCloseCode, _localCloseReason)); |
| 109 } |
| 110 } |
| 111 |
| 112 /// A [WebSocketSink] that tracks the close code and reason passed to [close]. |
| 113 class _HtmlWebSocketSink extends DelegatingStreamSink implements WebSocketSink { |
| 114 /// The channel to which this sink belongs. |
| 115 final HtmlWebSocketChannel _channel; |
| 116 |
| 117 _HtmlWebSocketSink(HtmlWebSocketChannel channel) |
| 118 : super(channel._controller.foreign.sink), |
| 119 _channel = channel; |
| 120 |
| 121 Future close([int closeCode, String closeReason]) { |
| 122 _channel._localCloseCode = closeCode; |
| 123 _channel._localCloseReason = closeReason; |
| 124 return super.close(); |
| 125 } |
| 126 } |
| 127 |
| 128 /// An enum for choosing what type [HtmlWebSocketChannel] emits for binary |
| 129 /// messages. |
| 130 class BinaryType { |
| 131 /// Tells the channel to emit binary messages as [Blob]s. |
| 132 static const blob = const BinaryType._("blob", "blob"); |
| 133 |
| 134 /// Tells the channel to emit binary messages as [Uint8List]s. |
| 135 static const list = const BinaryType._("list", "arraybuffer"); |
| 136 |
| 137 /// The name of the binary type, which matches its variable name. |
| 138 final String name; |
| 139 |
| 140 /// The value as understood by the underlying [WebSocket] API. |
| 141 final String value; |
| 142 |
| 143 const BinaryType._(this.name, this.value); |
| 144 |
| 145 String toString() => name; |
| 146 } |
OLD | NEW |