Writing a WebSocket server in C# - Web APIs

Writing a WebSocket server in C#
================================

If you would like to use the WebSocket API, it is useful if you have a server. In this article I will show you how to write one in C#. You can do it in any server-side language, but to keep things simple and more understandable, I chose Microsoft's language.

This server conforms to RFC 6455, so it will only handle connections from Chrome version 16, Firefox 11, IE 10 and over.

First steps
-----------

WebSockets communicate over a TCP (Transmission Control Protocol) connection. Luckily, C# has a TcpListener class which does as the name suggests. It is in the `System.Net.Sockets` namespace.

**Note:** It is a good idea to include the namespace with the `using` keyword in order to write less. It allows usage of a namespace's classes without typing the full namespace every time.

### TcpListener

Constructor:

```cs
TcpListener(System.Net.IPAddress localaddr, int port)
```

`localaddr` specifies the IP of the listener, and `port` specifies the port.

**Note:** To create an `IPAddress` object from a `string`, use the `Parse` static method of `IPAddress`.

Methods:

* `Start()`
* `System.Net.Sockets.TcpClient AcceptTcpClient()`
 Waits for a Tcp connection, accepts it and returns it as a TcpClient object.

Here's a barebones server implementation:

```cs
using System.Net.Sockets;
using System.Net;
using System;

class Server {
    public static void Main() {
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);

        server.Start();
        Console.WriteLine("Server has started on 127.0.0.1:80.{0}Waiting for a connection…", Environment.NewLine);

        TcpClient client = server.AcceptTcpClient();

        Console.WriteLine("A client connected.");
    }
}
```

### TcpClient

Methods:

* `System.Net.Sockets.NetworkStream GetStream()`
 Gets the stream which is the communication channel. Both sides of the channel have reading and writing capability.

Properties:

* `int Available`
 This Property indicates how many bytes of data have been sent. The value is zero until `NetworkStream.DataAvailable` is *true*.
### NetworkStream

Methods:

* Writes bytes from buffer, offset and size determine length of message.

```cs
Write(byte[] buffer, int offset, int size)
```

* Reads bytes to `buffer`. `offset` and `size` determine the length of the message.

```cs
Read(byte[] buffer, int offset, int size)
```

Let us extend our example.

```cs
TcpClient client = server.AcceptTcpClient();

Console.WriteLine("A client connected.");

NetworkStream stream = client.GetStream();

//enter to an infinite cycle to be able to handle every change in stream
while (true) {
    while (!stream.DataAvailable);

    byte[] bytes = new byte[client.Available];

    stream.Read(bytes, 0, bytes.Length);
}
```

Handshaking
-----------

When a client connects to a server, it sends a GET request to upgrade the connection to a WebSocket from a simple HTTP request. This is known as handshaking.

This sample code can detect a GET from the client. Note that this will block until the first 3 bytes of a message are available. Alternative solutions should be investigated for production environments.

```cs
using System.Text;
using System.Text.RegularExpressions;

while(client.Available < 3)
{
   // wait for enough bytes to be available
}

byte[] bytes = new byte[client.Available];

stream.Read(bytes, 0, bytes.Length);

//translate bytes of request to string
String data = Encoding.UTF8.GetString(bytes);

if (Regex.IsMatch(data, "^GET")) {

} else {

}
```

The response is easy to build, but might be a little difficult to understand. The full explanation of the Server handshake can be found in RFC 6455, section 4.2.2. For our purposes, we'll just build a simple response.

You must:

1. Obtain the value of the "Sec-WebSocket-Key" request header without any leading or trailing whitespace
2. Concatenate it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (a special GUID specified by RFC 6455)
3. Compute SHA-1 and Base64 hash of the new value
4. Write the hash back as the value of "Sec-WebSocket-Accept" response header in an HTTP response

```cs
if (new System.Text.RegularExpressions.Regex("^GET").IsMatch(data))
{
    const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker

    byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol
        + "Connection: Upgrade" + eol
        + "Upgrade: websocket" + eol
        + "Sec-WebSocket-Accept: " + Convert.ToBase64String(
            System.Security.Cryptography.SHA1.Create().ComputeHash(
                Encoding.UTF8.GetBytes(
                    new System.Text.RegularExpressions.Regex("Sec-WebSocket-Key: (.\*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
                )
            )
        ) + eol
        + eol);

    stream.Write(response, 0, response.Length);
}
```

Decoding messages
-----------------

After a successful handshake, the client will send encoded messages to the server.

If we send "MDN", we get these bytes:
```

129 131 61 84 35 6 112 16 109
```

Let's take a look at what these bytes mean.

The first byte, which currently has a value of 129, is a bitfield that breaks down as such:

| FIN (Bit 0) | RSV1 (Bit 1) | RSV2 (Bit 2) | RSV3 (Bit 3) | Opcode (Bit 4:7) |
| --- | --- | --- | --- | --- |
| 1 | 0 | 0 | 0 | 0x1=0001 |

* FIN bit: This bit indicates whether the full message has been sent from the client. Messages may be sent in frames, but for now we will keep things simple.
* RSV1, RSV2, RSV3: These bits must be 0 unless an extension is negotiated which supplies a nonzero value to them.
* Opcode: These bits describe the type of message received. Opcode 0x1 means this is a text message. Full list of Opcodes

The second byte, which currently has a value of 131, is another bitfield that breaks down as such:

| MASK (Bit 0) | Payload Length (Bit 1:7) |
| --- | --- |
| 1 | 0x83=0000011 |

* MASK bit: Defines whether the "Payload data" is masked. If set to 1, a masking key is present in Masking-Key, and this is used to unmask the "Payload data". All messages from the client to the server have this bit set.
* Payload Length: If this value is between 0 and 125, then it is the length of message. If it is 126, the following 2 bytes (16-bit unsigned integer) are the length. If it is 127, the following 8 bytes (64-bit unsigned integer) are the length.

**Note:** Because the first bit is always 1 for client-to-server messages, you can subtract 128 from this byte to get rid of the MASK bit.

Note that the MASK bit is set in our message. This means that the next four bytes (61, 84, 35, and 6) are the mask bytes used to decode the message. These bytes change with every message.

The remaining bytes are the encoded message payload.

### Decoding algorithm

*D\_i* = *E\_i* XOR *M*\_(*i* mod 4)

where *D* is the decoded message array, *E* is the encoded message array, *M* is the mask byte array, and *i* is the index of the message byte to decode.

Example in C#:

```cs
byte[] decoded = new byte[3];
byte[] encoded = new byte[3] {112, 16, 109};
byte[] mask = new byte[4] {61, 84, 35, 6};

for (int i = 0; i < encoded.Length; i++) {
    decoded[i] = (byte)(encoded[i] ^ mask[i % 4]);
}
```

Put together
------------

### wsserver.cs

```cs
//
// csc wsserver.cs
// wsserver.exe

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

class Server {
    public static void Main() {
        string ip = "127.0.0.1";
        int port = 80;
        var server = new TcpListener(IPAddress.Parse(ip), port);

        server.Start();
        Console.WriteLine("Server has started on {0}:{1}, Waiting for a connection…", ip, port);

        TcpClient client = server.AcceptTcpClient();
        Console.WriteLine("A client connected.");

        NetworkStream stream = client.GetStream();

        // enter to an infinite cycle to be able to handle every change in stream
        while (true) {
            while (!stream.DataAvailable);
            while (client.Available < 3); // match against "get"

            byte[] bytes = new byte[client.Available];
            stream.Read(bytes, 0, bytes.Length);
            string s = Encoding.UTF8.GetString(bytes);

            if (Regex.IsMatch(s, "^GET", RegexOptions.IgnoreCase)) {
                Console.WriteLine("=====Handshaking from client=====\n{0}", s);

                // 1. Obtain the value of the "Sec-WebSocket-Key" request header without any leading or trailing whitespace
                // 2. Concatenate it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (a special GUID specified by RFC 6455)
                // 3. Compute SHA-1 and Base64 hash of the new value
                // 4. Write the hash back as the value of "Sec-WebSocket-Accept" response header in an HTTP response
                string swk = Regex.Match(s, "Sec-WebSocket-Key: (.\*)").Groups[1].Value.Trim();
                string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
                byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka));
                string swkaSha1Base64 = Convert.ToBase64String(swkaSha1);

                // HTTP/1.1 defines the sequence CR LF as the end-of-line marker
                byte[] response = Encoding.UTF8.GetBytes(
                    "HTTP/1.1 101 Switching Protocols\r\n" +
                    "Connection: Upgrade\r\n" +
                    "Upgrade: websocket\r\n" +
                    "Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n");

                stream.Write(response, 0, response.Length);
            } else {
                bool fin = (bytes[0] & 0b10000000) != 0,
                    mask = (bytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set"
                int opcode = bytes[0] & 0b00001111, // expecting 1 - text message
                    offset = 2;
                ulong msglen = bytes[1] & (ulong)0b01111111;

                if (msglen == 126) {
                    // bytes are reversed because websocket will print them in Big-Endian, whereas
                    // BitConverter will want them arranged in little-endian on windows
                    msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
                    offset = 4;
                } else if (msglen == 127) {
                    // To test the below code, we need to manually buffer larger messages — since the NIC's autobuffering
                    // may be too latency-friendly for this code to run (that is, we may have only some of the bytes in this
                    // websocket frame available through client.Available).
                    msglen = BitConverter.ToUInt64(new byte[] { bytes[9], bytes[8], bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2] },0);
                    offset = 10;
                }

                if (msglen == 0) {
                    Console.WriteLine("msglen == 0");
                } else if (mask) {
                    byte[] decoded = new byte[msglen];
                    byte[] masks = new byte[4] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] };
                    offset += 4;

                    for (ulong i = 0; i < msglen; ++i)
                        decoded[i] = (byte)(bytes[offset + i] ^ masks[i % 4]);

                    string text = Encoding.UTF8.GetString(decoded);
                    Console.WriteLine("{0}", text);
                } else
                    Console.WriteLine("mask bit not set");

                Console.WriteLine();
            }
        }
    }
}
```

### client.html

```html
<!doctype html>
<html lang="en">
  <style>
 textarea {
 vertical-align: bottom;
 }
 #output {
 overflow: auto;
 }
 #output > p {
 overflow-wrap: break-word;
 }
 #output span {
 color: blue;
 }
 #output span.error {
 color: red;
 }
 </style>
  <body>
    <h2>WebSocket Test</h2>
    <textarea cols="60" rows="6"></textarea>
    <button>send</button>
    <div id="output"></div>
  </body>
  <script>
 // http://www.websocket.org/echo.html
 const button = document.querySelector("button");
 const output = document.querySelector("#output");
 const textarea = document.querySelector("textarea");
 const wsUri = "ws://127.0.0.1/";
 const websocket = new WebSocket(wsUri);

 button.addEventListener("click", onClickButton);

 websocket.onopen = (e) => {
 writeToScreen("CONNECTED");
 doSend("WebSocket rocks");
 };

 websocket.onclose = (e) => {
 writeToScreen("DISCONNECTED");
 };

 websocket.onmessage = (e) => {
 writeToScreen(`<span>RESPONSE: ${e.data}</span>`);
 };

 websocket.onerror = (e) => {
 writeToScreen(`<span class="error">ERROR:</span> ${e.data}`);
 };

 function doSend(message) {
 writeToScreen(`SENT: ${message}`);
 websocket.send(message);
 }

 function writeToScreen(message) {
 output.insertAdjacentHTML("afterbegin", `<p>${message}</p>`);
 }

 function onClickButton() {
 const text = textarea.value;

 text && doSend(text);
 textarea.value = "";
 textarea.focus();
 }
 </script>
</html>
```

Related
-------

* Writing WebSocket servers


Writing a WebSocket server in Java - Web APIs

Writing a WebSocket server in Java
==================================

This example shows you how to create a WebSocket API server using Oracle Java.

Although other server-side languages can be used to create a WebSocket server, this example uses Oracle Java to simplify the example code.

This server conforms to RFC 6455, so it only handles connections from Chrome version 16, Firefox 11, IE 10 and higher.

First steps
-----------

WebSockets communicate over a TCP (Transmission Control Protocol) connection. Java's ServerSocket class is located in the `java.net` package.

### ServerSocket

The `ServerSocket` constructor accepts a single parameter `port` of type `int`.

When you instantiate the ServerSocket class, it is bound to the port number you specified by the *port* argument.

Here's an implementation split into parts:

```java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class WebSocket {
  public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
    ServerSocket server = new ServerSocket(80);
    try {
      System.out.println("Server has started on 127.0.0.1:80.\r\nWaiting for a connection…");
      Socket client = server.accept();
      System.out.println("A client connected.");
```

### Socket Methods

`java.net.Socket.getInputStream()`

Returns an input stream for this socket.

`java.net.Socket.getOutputStream()`

Returns an output stream for this socket.

### OutputStream Methods

```java
write(byte[] b, int off, int len)
```

Writes `len` bytes from the specified byte array starting at offset `off` to this output stream.

### InputStream Methods

```java
read(byte[] b, int off, int len)
```

Reads up to *len* bytes of data from the input stream into an array of bytes.

Let us extend our example.

```java
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
Scanner s = new Scanner(in, "UTF-8");
```

Handshaking
-----------

When a client connects to a server, it sends a GET request to upgrade the connection to a WebSocket from a simple HTTP request. This is known as handshaking.

```java
try {
  String data = s.useDelimiter("\\r\\n\\r\\n").next();
  Matcher get = Pattern.compile("^GET").matcher(data);
```

Creating the response is easier than understanding why you must do it in this way.

You must,

1. Obtain the value of *Sec-WebSocket-Key* request header without any leading and trailing whitespace
2. Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
3. Compute SHA-1 and Base64 code of it
4. Write it back as value of *Sec-WebSocket-Accept* response header as part of an HTTP response.

```java
if (get.find()) {
  Matcher match = Pattern.compile("Sec-WebSocket-Key: (.\*)").matcher(data);
  match.find();
  byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
    + "Connection: Upgrade\r\n"
    + "Upgrade: websocket\r\n"
    + "Sec-WebSocket-Accept: "
    + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((match.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes("UTF-8")))
    + "\r\n\r\n").getBytes("UTF-8");
  out.write(response, 0, response.length);
```

Decoding messages
-----------------

After a successful handshake, client can send messages to the server, but now these are encoded.

If we send "abcdef", we get these bytes:
```

129 134 167 225 225 210 198 131 130 182 194 135
```

* 129:

| FIN (Is this the whole message?) | RSV1 | RSV2 | RSV3 | Opcode |
| --- | --- | --- | --- | --- |
| 1 | 0 | 0 | 0 | 0x1=0001 |

FIN: You can send your message in frames, but now keep things simple.
 Opcode *0x1* means this is a text. Full list of Opcodes
* 134:
 If the second byte minus 128 is between 0 and 125, this is the length of the message. If it is 126, the following 2 bytes (16-bit unsigned integer), if 127, the following 8 bytes (64-bit unsigned integer, the most significant bit MUST be 0) are the length.

**Note:** It can take 128 because the first bit is always 1.
* 167, 225, 225 and 210 are the bytes of the key to decode. It changes every time.
* The remaining encoded bytes are the message.
### Decoding algorithm

decoded byte = encoded byte XOR (position of encoded byte BITWISE AND 0x3)th byte of key

Example in Java:

```java
          byte[] decoded = new byte[6];
          byte[] encoded = new byte[] { (byte) 198, (byte) 131, (byte) 130, (byte) 182, (byte) 194, (byte) 135 };
          byte[] key = new byte[] { (byte) 167, (byte) 225, (byte) 225, (byte) 210 };
          for (int i = 0; i < encoded.length; i++) {
            decoded[i] = (byte) (encoded[i] ^ key[i & 0x3]);
          }
        }
      } finally {
        s.close();
      }
    } finally {
      server.close();
    }
  }
}
```

Related
-------

* Writing WebSocket servers


Writing WebSocket client applications - Web APIs

Writing WebSocket client applications
=====================================

WebSocket client applications use the WebSocket API to communicate with WebSocket servers using the WebSocket protocol.

**Note:** This feature is available in Web Workers

**Note:** The example snippets in this article are taken from our WebSocket chat client/server sample.
 See the code.

Creating a WebSocket object
---------------------------

In order to communicate using the WebSocket protocol, you need to create a `WebSocket` object; this will automatically attempt to open the connection to the server.

The WebSocket constructor accepts one required and one optional parameter:

```js
webSocket = new WebSocket(url, protocols);
```

`url`

 The URL to which to connect; this should be the URL to which the WebSocket server will respond.
 This should use the URL scheme `wss://`, although some software may allow you to use the insecure `ws://` for local connections.

`protocols` Optional

 Either a single protocol string or an array of protocol strings.
 These strings are used to indicate sub-protocols, so that a single server can implement multiple WebSocket sub-protocols (for example, you might want one server to be able to handle different types of interactions depending on the specified `protocol`).
 If you don't specify a protocol string, an empty string is assumed.

 The constructor will throw a `SecurityError` if the destination doesn't allow access.
 This may happen if you attempt to use an insecure connection (most user agents now require a secure link for all WebSocket connections unless they're on the same device or possibly on the same network).

### Connection errors

If an error occurs while attempting to connect, first a simple event with the name `error` is sent to the `WebSocket` object (thereby invoking its `onerror` handler), and then the `CloseEvent` is sent to the `WebSocket` object (thereby invoking its `onclose` handler) to indicate the reason for the connection's closing.

The browser may also output to its console a more descriptive error message as well as a closing code as defined in RFC 6455, Section 7.4 through the `CloseEvent`.

### Examples

 This simple example creates a new WebSocket, connecting to the server at `wss://www.example.com/socketserver`.
 A custom protocol of "protocolOne" is named in the request for the socket in this example, though this can be omitted.

```js
const exampleSocket = new WebSocket(
  "wss://www.example.com/socketserver",
  "protocolOne",
);
```

 On return, `exampleSocket.readyState` is `CONNECTING`. The `readyState` will become `OPEN` once
 the connection is ready to transfer data.

If you want to open a connection and are flexible about the protocols you support, you can specify an array of protocols:

```js
const exampleSocket = new WebSocket("wss://www.example.com/socketserver", [
  "protocolOne",
  "protocolTwo",
]);
```

Once the connection is established (that is, `readyState` is `OPEN`), `exampleSocket.protocol` will tell you which protocol the server selected.

Establishing a WebSocket relies on the HTTP Upgrade mechanism, so the request for the protocol upgrade is implicit when we address the web server as `ws://www.example.com` or `wss://www.example.com`.

Sending data to the server
--------------------------

 Once you've opened your connection, you can begin transmitting data to the server.
 To do this, call the `WebSocket` object's `send()` method for each message you want to send:

```js
exampleSocket.send("Here's some text that the server is urgently awaiting!");
```

You can send data as a string, `Blob`, or `ArrayBuffer`.

 As establishing a connection is asynchronous and prone to failure there is no guarantee that calling the `send()` method immediately after creating a WebSocket object will be successful.
 We can at least be sure that attempting to send data only takes place once a connection is established by defining an `onopen` event handler to do the work:

```js
exampleSocket.onopen = (event) => {
  exampleSocket.send("Here's some text that the server is urgently awaiting!");
};
```

### Using JSON to transmit objects

 One handy thing you can do is use JSON to send reasonably complex data
 to the server. For example, a chat program can interact with a server using a protocol
 implemented using packets of JSON-encapsulated data:

```js
// Send text to all users through the server
function sendText() {
  // Construct a msg object containing the data the server needs to process the message from the chat client.
  const msg = {
    type: "message",
    text: document.getElementById("text").value,
    id: clientID,
    date: Date.now(),
  };

  // Send the msg object as a JSON-formatted string.
  exampleSocket.send(JSON.stringify(msg));

  // Blank the text input element, ready to receive the next line of text from the user.
  document.getElementById("text").value = "";
}
```

Receiving messages from the server
----------------------------------

 WebSockets is an event-driven API; when messages are received, a `message`
 event is sent to the `WebSocket` object. To handle it, add an event listener
 for the `message` event, or use the `onmessage` event handler. To begin listening for incoming data, you can do something
 like this:

```js
exampleSocket.onmessage = (event) => {
  console.log(event.data);
};
```

### Receiving and interpreting JSON objects

Let's consider the chat client application first alluded to in Using JSON to transmit objects. There are assorted types of data packets the client might receive, such as:

* Login handshake
* Message text
* User list updates

The code that interprets these incoming messages might look like this:

```js
exampleSocket.onmessage = (event) => {
  const f = document.getElementById("chatbox").contentDocument;
  let text = "";
  const msg = JSON.parse(event.data);
  const time = new Date(msg.date);
  const timeStr = time.toLocaleTimeString();

  switch (msg.type) {
    case "id":
      clientID = msg.id;
      setUsername();
      break;
    case "username":
      text = `User <em>${msg.name}</em> signed in at ${timeStr}<br>`;
      break;
    case "message":
      text = `(${timeStr}) ${msg.name} : ${msg.text} <br>`;
      break;
    case "rejectusername":
      text = `Your username has been set to <em>${msg.name}</em> because the name you chose is in use.<br>`;
      break;
    case "userlist":
      document.getElementById("userlistbox").innerHTML = msg.users.join("<br>");
      break;
  }

  if (text.length) {
    f.write(text);
    document.getElementById("chatbox").contentWindow.scrollByPages(1);
  }
};
```

Here we use `JSON.parse()` to convert the JSON object back into the original object, then examine and act upon its contents.

### Text data format

Text received over a WebSocket connection is in UTF-8 format.

Closing the connection
----------------------

When you've finished using the WebSocket connection, call the WebSocket method `close()`:

```js
exampleSocket.close();
```

 It may be helpful to examine the socket's `bufferedAmount` attribute before attempting to close the connection to determine if any data has yet to be transmitted on the network.
 If this value isn't 0, there's pending data still, so you may wish to wait before closing the connection.

Security considerations
-----------------------

 WebSockets should not be used in a mixed content environment; that is, you shouldn't open a non-secure WebSocket connection from a page loaded using HTTPS or vice versa.
 Most browsers now only allow secure WebSocket connections, and no longer support using them in insecure contexts.


MediaStreamAudioDestinationNode: stream property - Web APIs

MediaStreamAudioDestinationNode: stream property
================================================

The `stream` property of the `AudioContext` interface represents a `MediaStream` containing a single audio `MediaStreamTrack` with the same number of channels as the node itself.

You can use this property to get a stream out of the audio graph and feed it into another construct, such as a Media Recorder.

Value
-----

A `MediaStream` containing a single audio track. The audio track is a `MediaStreamTrack` whose `kind` is `audio`.

Examples
--------

See `AudioContext.createMediaStreamDestination()` for example code that creates a `MediaStreamAudioDestinationNode` and uses its `stream` property as a source for audio to be recorded.

Specifications
--------------

| Specification |
| --- |
| Web Audio API # dom-mediastreamaudiodestinationnode-stream |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* Using the Web Audio API


MediaStreamAudioDestinationNode: MediaStreamAudioDestinationNode() constructor - Web APIs

MediaStreamAudioDestinationNode: MediaStreamAudioDestinationNode() constructor
==============================================================================

The **`MediaStreamAudioDestinationNode()`** constructor of the Web Audio API creates a new `MediaStreamAudioDestinationNode` object instance.

Syntax
------

```js
new MediaStreamAudioDestinationNode(context)
new MediaStreamAudioDestinationNode(context, options)
```

### Parameters

`context`

An `AudioContext` representing the audio context you want the node to be associated with.

`options` Optional

An object defining the properties you want the `MediaStreamAudioDestinationNode` to have:

`channelCount`

 An integer used to determine how many channels are used when
 up-mixing and down-mixing
 connections to any inputs to the node. (See
 `AudioNode.channelCount` for more information.) Its usage and precise
 definition depend on the value of `channelCountMode`.

`channelCountMode`

 A string describing the way channels must be matched between
 the node's inputs and outputs. (See `AudioNode.channelCountMode` for more
 information including default values.)

`channelInterpretation`

 A string describing the meaning of the channels. This interpretation will define how audio
 up-mixing and down-mixing
 will happen. The possible values are `"speakers"` or `"discrete"`. (See
 `AudioNode.channelCountMode` for more information including default
 values.)

Examples
--------

```js
const ac = new AudioContext();

const myDestination = new MediaStreamAudioDestinationNode(ac);
```

Specifications
--------------

| Specification |
| --- |
| Web Audio API # dom-mediastreamaudiodestinationnode-mediastreamaudiodestinationnode |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


HTMLHtmlElement: version property - Web APIs

HTMLHtmlElement: version property
=================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

**Note:** This property has been declared as deprecated by the W3C technical recommendation for HTML 4.01 in favor of use of the DTD for obtaining version information for a document.

Returns version information about the document type definition (DTD) of a document. While this property is recognized by Mozilla, the return value for this property is always an empty string.

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


XRSystem: devicechange event - Web APIs

XRSystem: devicechange event
============================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

A **`devicechange`** event is fired on an `XRSystem` object whenever the availability of immersive XR devices has changed; for example, a VR headset or AR goggles have been connected or disconnected. It's a generic `Event` with no added properties.

**Note:** Not to be confused with the `MediaDevices` `devicechange` event.

Syntax
------

Use the event name in methods like `addEventListener()`, or set an event handler property.

```js
addEventListener("devicechange", (event) => {});

ondevicechange = (event) => {};
```

If the use of WebXR has been blocked by an `xr-spatial-tracking` Permissions Policy, `devicechange` events will not fire.

Event type
----------

A generic `Event` with no added properties.

Description
-----------

### Trigger

Triggered whenever the availability of immersive XR devices has changed. For example, when a VR headset or AR goggles have been connected or disconnected.

### Use cases

You can use this event to, for example, monitor for the availability of a WebXR-compatible device so that you can enable a UI element which the user can use to activate immersive mode. This is shown in the example below.

Examples
--------

The example shown here handles the `devicechange` event by toggling the availability of the "Enter XR" button based on whether or not any immersive devices are currently available.

```js
if (navigator.xr) {
  navigator.xr.addEventListener("devicechange", (event) => {
    navigator.xr.isSessionSupported("immersive-vr").then((immersiveOK) => {
      enableXRButton.disabled = !immersiveOK;
    });
  });
}
```

When `devicechange` is received, the handler set up in this code calls the `XR` method `isSessionSupported()` to find out if there's a device available that can handle immersive VR presentations. If there is, the button to enter XR mode is enabled; otherwise it's disabled.

You can also use the `ondevicechange` event handler property to set a single handler for `devicechange` events:

```js
if (navigator.xr) {
  navigator.xr.ondevicechange = (event) => {
    // …
  };
}
```

Specifications
--------------

| Specification |
| --- |
| WebXR Device API # eventdef-xrsystem-devicechange |
| WebXR Device API # dom-xrsystem-ondevicechange |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


XRSystem: requestSession() method - Web APIs

XRSystem: requestSession() method
=================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`XRSystem`** interface's
 **`requestSession()`** method returns a `promise`
 which resolves to an `XRSession` object through which you can manage the
 requested type of WebXR session.

 While only one immersive VR session can be active at a time, multiple
 inline sessions can be in progress at once.

Syntax
------

```js
requestSession(mode)
requestSession(mode, options)
```

### Parameters

`mode`

A `String` defining the XR session mode. The supported modes are:

* Experimental
 `immersive-ar`: The session's output will be given exclusive access to the immersive device,
 but the rendered content will be blended with the real-world environment.
 The session's `environmentBlendMode` indicates the method
 to be used to blend the content together.
* `immersive-vr`: Indicates that the rendered session will be displayed using an immersive XR device
 in VR mode; it is not intended to be overlaid or integrated into the surrounding environment.
 The `environmentBlendMode` is expected to be
 `opaque` if possible, but might be `additive` if the hardware requires it.
* `inline`: The output is presented inline within the context of an element in a standard HTML document,
 rather than occupying the full visual space. Inline sessions can be presented in either mono or stereo mode,
 and may or may not have viewer tracking available. Inline sessions don't require special hardware and should be
 available on any user agent offering WebXR API support.

`options` Optional

An object to configure the `XRSession`. If none are included, the device will use a default feature configuration for all options.

* `requiredFeatures` Optional: An array of values which the returned `XRSession`
*must* support. See Session features below.
* `optionalFeatures` Optional: An array of values identifying features which the returned
 `XRSession` may optionally support. See Session features below.
* `domOverlay` Optional: An object with a required `root` property that specifies the overlay element that will be displayed to the user as the content of the DOM overlay. See the example below.
* `depthSensing` Optional: An object with two required properties `usagePreference` and `dataFormatPreference` to configure how to perform depth sensing. See the example below.

### Return value

 A `Promise` that resolves with an `XRSession` object if the
 device and user agent support the requested mode and features.

### Exceptions

 This method doesn't throw true exceptions; instead, it rejects the returned promise,
 passing into it a `DOMException` whose `name` is one of the
 following:

`InvalidStateError` `DOMException`

 Returned if the requested session mode is `immersive-vr` but there is already an
 immersive VR session either currently active or in the process of being set up. There
 can only be one immersive VR session at a time.

`NotSupportedError` `DOMException`

 Returned if there is no WebXR-compatible device available, or the device does not support the
 specified `sessionMode`; this can also be thrown if any of the
 *required* options are unsupported.

`SecurityError` `DOMException`

Returned if permission to enter the specified XR mode is denied. This can happen for several reasons, which are covered in more detail in Permissions and security.

Session features
----------------

The following session features and reference spaces can be requested, either as `optionalFeatures` or `requiredFeatures`.

`anchors`

Enable use of `XRAnchor` objects.

`bounded-floor`

Similar to the `local` type, except the user is not expected to move outside a predetermined boundary, given by the `boundsGeometry` in the returned object.

`depth-sensing`

Enable the ability to obtain depth information using `XRDepthInformation` objects.

`dom-overlay`

Enable allowing to specify a DOM overlay element that will be displayed to the user.

`hand-tracking`

Enable articulated hand pose information from hand-based input controllers (see `XRHand` and `XRInputSource.hand`).

`hit-test`

Enable hit testing features for performing hit tests against real-world geometry.

`layers`

Enable the ability to create various layer types (other than `XRProjectionLayer`).

`light-estimation`

Enable the ability to estimate environmental lighting conditions using `XRLightEstimate` objects.

`local`

Enable a tracking space whose native origin is located near the viewer's position at the time the session was created. The exact position depends on the underlying platform and implementation. The user isn't expected to move much if at all beyond their starting position, and tracking is optimized for this use case.

`local-floor`

Similar to the `local` type, except the starting position is placed in a safe location for the viewer to stand, where the value of the y axis is 0 at floor level. If that floor level isn't known, the user agent will estimate the floor level. If the estimated floor level is non-zero, the browser is expected to round it such a way as to avoid fingerprinting (likely to the nearest centimeter).

`secondary-views`

Enable `XRView` objects to be secondary views. This can be used for first-person observer views used for video capture, or "quad views" where there are two views per eye, with differing resolution and fields of view.

`unbounded`

Enable a tracking space which allows the user total freedom of movement, possibly over extremely long distances from their origin point. The viewer isn't tracked at all; tracking is optimized for stability around the user's current position, so the native origin may drift as needed to accommodate that need.

`viewer`

Enable a tracking space whose native origin tracks the viewer's position and orientation.

Security
--------

Several session features and the various reference spaces have minimum security and privacy requirements, like asking for user consent and/or requiring the `Permissions-Policy`: `xr-spatial-tracking` directive to be set. See also Permissions and security for more details.

| Session feature | User consent requirement | Permissions policy requirement |
| --- | --- | --- |
| `bounded-floor` | Always required | `xr-spatial-tracking` |
| `depth-sensing` | — | `xr-spatial-tracking` |
| `hand-tracking` | Always required | — |
| `hit-test` | — | `xr-spatial-tracking` |
| `local` | Always required for inline sessions | `xr-spatial-tracking` |
| `local-floor` | Always required | `xr-spatial-tracking` |
| `unbounded` | Always required | `xr-spatial-tracking` |
| `viewer` | Always required | — |

See also transient user activation.

Examples
--------

### Creating an immersive VR session

 The following example calls `requestSession()` requesting an
 `"immersive-vr"` session. If the `Promise` resolves, it sets up a
 session and initiates the animation loop.

```js
navigator.xr
  .requestSession("immersive-vr")
  .then((xrSession) => {
    xrSession.addEventListener("end", onXRSessionEnded);
    // Do necessary session setup here.
    // Begin the session's animation loop.
    xrSession.requestAnimationFrame(onXRAnimationFrame);
  })
  .catch((error) => {
    // "immersive-vr" sessions are not supported
    console.error(
      "'immersive-vr' isn't supported, or an error occurred activating VR!",
    );
  });
```

### Verifying WebXR support and using a button to start VR mode

 The following example shows how to use both `isSessionSupported()` and
 `requestSession()`. First, it checks to see if WebXR is available by
 verifying the existence of `navigator.xr`. Next, it calls
 `isSessionSupported()`, passing it the desired session option before enabling
 controls for entering XR. Adding controls is a necessary step because entering XR
 requires a user action. Finally, the `onButtonClicked()` method calls
 `requestSession()` using the same session option passed to
 `isSessionSupported()`.

```js
if (navigator.xr) {
  navigator.xr.isSessionSupported("immersive-vr").then((isSupported) => {
    if (isSupported) {
      immersiveButton.addEventListener("click", onButtonClicked);
      immersiveButton.textContent = "Enter XR";
      immersiveButton.disabled = false;
    } else {
      console.error("WebXR doesn't support immersive-vr mode!");
    }
  });
} else {
  console.error("WebXR is not available!");
}

function onButtonClicked() {
  if (!xrSession) {
    navigator.xr.requestSession("immersive-vr").then((session) => {
      xrSession = session;
      // onSessionStarted() not shown for reasons of brevity and clarity.
      onSessionStarted(xrSession);
    });
  } else {
    // Button is a toggle button.
    xrSession.end().then(() => (xrSession = null));
  }
}
```

### Requesting a session with required features

Require an unbounded experience in which the user is able to freely move around their physical environment:

```js
navigator.xr.requestSession("immersive-vr", {
  requiredFeatures: ["unbounded"],
});
```

### Requesting a session with a DOM overlay

```js
navigator.xr.requestSession("immersive-ar", {
  optionalFeatures: ["dom-overlay"],
  domOverlay: {
    root: document.getElementById("xr-overlay"),
  },
});
```

### Requesting a depth-sensing session

Here, the caller is able to handle both CPU- and GPU-optimized usage, as well as both "luminance-alpha" and "float32" formats. The order indicates preference for CPU and "luminance-alpha":

```js
navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["depth-sensing"],
  depthSensing: {
    usagePreference: ["cpu-optimized", "gpu-optimized"],
    dataFormatPreference: ["luminance-alpha", "float32"],
  },
});
```

Specifications
--------------

| Specification |
| --- |
| WebXR Device API # dom-xrsystem-requestsession |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


XRSystem: isSessionSupported() method - Web APIs

XRSystem: isSessionSupported() method
=====================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The `XRSystem` method
 **`isSessionSupported()`** returns a promise which resolves to
 `true` if the specified WebXR session mode is supported by the user's WebXR
 device. Otherwise, the promise resolves with `false`.

 If no devices are available or the browser doesn't have permission
 to use the XR device, the promise is rejected with an appropriate
 `DOMException`.

Syntax
------

```js
isSessionSupported(mode)
```

### Parameters

`mode`

 A `String` specifying the WebXR session mode for which support is to
 be checked. Possible modes to check for:

* `immersive-ar`
Experimental
* `immersive-vr`
* `inline`

### Return value

 A `Promise` that resolves to `true` if the specified session
 mode is supported; otherwise the promise resolves to `false`.

### Exceptions

 Rather than throwing true exceptions, `isSessionSupported()` rejects the
 returned promise, passing to the rejection handler a `DOMException` whose
 `name` is one of the following strings.

`SecurityError`

Use of this feature is blocked by an `xr-spatial-tracking` Permissions Policy.

Examples
--------

 In this example, we see `isSessionSupported()` used to detect whether or not
 the device supports VR mode by checking to see if an `immersive-vr` session
 is supported. If it is, we set up a button to read "Enter XR", to call a method
 `onButtonClicked()`, and enable the button.

 If no session is already underway, we request the VR session and, if successful, set up
 the session in a method called `onSessionStarted()`, not shown. If a session
 *is* already underway when the button is clicked, we call the
 `xrSession` object's `end()` method to shut
 down the WebXR session.

```js
if (navigator.xr) {
  navigator.xr.isSessionSupported("immersive-vr").then((isSupported) => {
    if (isSupported) {
      userButton.addEventListener("click", onButtonClicked);
      userButton.textContent = "Enter XR";
      userButton.disabled = false;
    }
  });
}

function onButtonClicked() {
  if (!xrSession) {
    navigator.xr.requestSession("immersive-vr").then((session) => {
      xrSession = session;
      // onSessionStarted() not shown for reasons of brevity and clarity.
      onSessionStarted(xrSession);
    });
  } else {
    // Button is a toggle button.
    xrSession.end();
  }
}
```

Specifications
--------------

| Specification |
| --- |
| WebXR Device API # dom-xrsystem-issessionsupported |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


XRWebGLDepthInformation: texture property - Web APIs

XRWebGLDepthInformation: texture property
=========================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

The *read-only* **`texture`** property of the `XRWebGLDepthInformation` interface is a `WebGLTexture` containing depth buffer information as an opaque texture.

Value
-----

A `WebGLTexture`.

Examples
--------

Use `XRWebGLBinding.getDepthInformation()` to obtain GPU depth information. The returned `XRWebGLDepthInformation` object will contain the `texture` buffer which can then be bound to a texture and depth buffer information can be made available to a WebGL fragment shader.

```js
const depthInfo = glBinding.getDepthInformation(view);
const uvTransform = depthInfo.normDepthBufferFromNormView.matrix;

const u_DepthTextureLocation = gl.getUniformLocation(program, "u\_DepthTexture");
const u_UVTransformLocation = gl.getUniformLocation(program, "u\_UVTransform");
const u_RawValueToMeters = gl.getUniformLocation(program, "u\_RawValueToMeters");

gl.bindTexture(gl.TEXTURE\_2D, depthInfo.texture);
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(u_DepthTextureLocation, 0);

// UV transform to correctly index into the depth map
gl.uniformMatrix4fv(u_UVTransformLocation, false, uvTransform);

// scaling factor to convert from the raw number to meters
gl.uniform1f(u_RawValueToMeters, depthInfo.rawValueToMeters);
```

Specifications
--------------

| Specification |
| --- |
| WebXR Depth Sensing Module # dom-xrwebgldepthinformation-texture |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `XRWebGLBinding.getDepthInformation()`
* `WebGLRenderingContext.bindTexture()`


ByteLengthQueuingStrategy: highWaterMark property - Web APIs

ByteLengthQueuingStrategy: highWaterMark property
=================================================

The read-only **`ByteLengthQueuingStrategy.highWaterMark`** property returns the total number of bytes that can be contained in the internal queue before backpressure is applied.

**Note:** Unlike `CountQueuingStrategy()` where the `highWaterMark` property specifies a simple count of the number of chunks, with `ByteLengthQueuingStrategy()`, the `highWaterMark` parameter specifies a number of *bytes* — specifically, given a stream of chunks, how many bytes worth of those chunks (rather than a count of how many of those chunks) can be contained in the internal queue before backpressure is applied.

Values
------

An integer.

Examples
--------

```js
const queuingStrategy = new ByteLengthQueuingStrategy({
  highWaterMark: 1 \* 1024,
});

const readableStream = new ReadableStream(
  {
    start(controller) {
      // …
    },
    pull(controller) {
      // …
    },
    cancel(err) {
      console.log("stream error:", err);
    },
  },
  queuingStrategy,
);

const size = queuingStrategy.size(chunk);
console.log(`highWaterMark value: ${queuingStrategy.highWaterMark}$`);
```

Specifications
--------------

| Specification |
| --- |
| Streams Standard # ref-for-blqs-high-water-mark① |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `ByteLengthQueuingStrategy()` constructor


ByteLengthQueuingStrategy: size() method - Web APIs

ByteLengthQueuingStrategy: size() method
========================================

 The **`size()`** method of the
 `ByteLengthQueuingStrategy` interface returns the given chunk's
 `byteLength` property.

Syntax
------

```js
size(chunk)
```

### Parameters

`chunk`

A chunk of data being passed through the stream.

### Return value

An integer representing the byte length of the given chunk.

Examples
--------

```js
const queuingStrategy = new ByteLengthQueuingStrategy({ highWaterMark: 1 });

const readableStream = new ReadableStream(
  {
    start(controller) {
      // …
    },
    pull(controller) {
      // …
    },
    cancel(err) {
      console.log("stream error:", err);
    },
  },
  queuingStrategy,
);

const size = queueingStrategy.size(chunk);
```

Specifications
--------------

| Specification |
| --- |
| Streams Standard # blqs-size |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `ByteLengthQueuingStrategy()` constructor


ByteLengthQueuingStrategy: ByteLengthQueuingStrategy() constructor - Web APIs

ByteLengthQueuingStrategy: ByteLengthQueuingStrategy() constructor
==================================================================

 The **`ByteLengthQueuingStrategy()`**
 constructor creates and returns a `ByteLengthQueuingStrategy` object
 instance.

Syntax
------

```js
new ByteLengthQueuingStrategy(highWaterMark)
```

### Parameters

An object with the following property:

`highWaterMark`

The total number of bytes that can be contained in the internal queue before backpressure is applied.

Unlike `CountQueuingStrategy()` where the `highWaterMark` parameter specifies a simple count of the number of chunks, with `ByteLengthQueuingStrategy()`, the `highWaterMark` parameter specifies a number of *bytes* — specifically, given a stream of chunks, how many bytes worth of those chunks (rather than a count of how many of those chunks) can be contained in the internal queue before backpressure is applied.

### Return value

An instance of the `ByteLengthQueuingStrategy` object.

### Exceptions

None.

Examples
--------

```js
const queuingStrategy = new ByteLengthQueuingStrategy({
  highWaterMark: 1 \* 1024,
});

const readableStream = new ReadableStream(
  {
    start(controller) {
      // …
    },
    pull(controller) {
      // …
    },
    cancel(err) {
      console.log("stream error:", err);
    },
  },
  queuingStrategy,
);

const size = queuingStrategy.size(chunk);
```

Specifications
--------------

| Specification |
| --- |
| Streams Standard # blqs-constructor |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `ByteLengthQueuingStrategy` interface


RTCIceCandidatePairStats: availableOutgoingBitrate property - Web APIs

RTCIceCandidatePairStats: availableOutgoingBitrate property
===========================================================

 The `RTCIceCandidatePairStats` property
 **`availableOutgoingBitrate`** returns a value indicative of
 the available outbound capacity of the network connection represented by the candidate
 pair. The higher the value, the more bandwidth you can assume is available for
 outgoing data.

 You can get the incoming available bitrate from
 `availableIncomingBitrate`.

Value
-----

 A floating-point value which approximates the amount of available bandwidth for
 outgoing data on the network connection described by the
 `RTCIceCandidatePair`. The value is reported in bits per second and is
 computed over a 1-second interval.

The returned value is `undefined` in each of the following situations:

* The underlying implementation doesn't support computing a sender-side estimate of
 the outgoing bit rate.
* The `RTCIceCandidatePair` described by this object has never been used.
* The candidate pair was once in use, but no longer is.

 The value returned is calculated by adding up the available bit rate for every
 RTP stream using the connection described by this candidate pair. The
 returned value doesn't take into account overhead introduced by underlying protocols,
 including IP, UDP, or TCP.

**Note:** The returned value is computed using a method similar—but not
 identical—to the Transport Independent Application Specific Maximum (TIAS) described
 in RFC 3890, section 6.2.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-availableoutgoingbitrate |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: selected property - Web APIs

RTCIceCandidatePairStats: selected property
===========================================

**Non-standard:** This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.

The *non-standard*, Firefox-specific `RTCIceCandidatePairStats` property **`selected`** indicates whether or not the candidate pair described by the object is the one currently being used to communicate with the remote peer.

Syntax
------

```js
icpStats.selected
```

### Value

A Firefox-specific Boolean value which is `true` if the candidate pair described by this object is the one currently in use.

In any other browser, you can determine the selected candidate pair by looking for a stats object of type `transport`, which is an `RTCTransportStats` object. That object's `selectedCandidatePairId` property indicates whether or not the specified transport is the one being used.

Example
-------

The function shown in this example identifies the currently-selected candidate pair from a statistics report by first iterating over each report, looking for a `transport` report; when one is found, that transport's `selectedCandidatePairId` is used to get the `RTCIceCandidatePair` describing the connection.

If that fails, then the second section iterates over the reports, looking for a `candidate-pair` record whose Firefox-specific `selected` property is `true`. This candidate pair is then returned as the currently-selected one.

```js
function getCurrentCandidatePair(statsResults) {
  statsResults.forEach((report) => {
    if (report.type === "transport") {
      currentPair = statsResults.get(report.selectedCandidatePairId);
    }
  });

  if (!currentPair) {
    statsResults.forEach((report) => {
      if (report.type === "candidate-pair" && report.selected) {
        currentPair = report;
      }
    });
  }

  return currentPair;
}
```

Specifications
--------------

Not part of any specification. This property is unique to Firefox.

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: currentRoundTripTime property - Web APIs

RTCIceCandidatePairStats: currentRoundTripTime property
=======================================================

 The `RTCIceCandidatePairStats` property
 **`currentRoundTripTime`** is a floating-point value
 indicating the number of seconds it takes for data to be sent by this peer to the
 remote peer and back over the connection described by this pair of ICE
 candidates.

Syntax
------

```js
rtt = rtcIceCandidatePairStats.currentRoundTripTime
```

### Value

 A floating-point value indicating the round-trip time, in seconds for the connection
 described by the pair of candidates for which this `RTCIceCandidatePairStats`
 object offers statistics.

 This value is computed by observing the time that elapsed between the most recent
 STUN request being sent to the remote peer and the response to that
 request arriving. This information may come from ongoing STUN connectivity checks as
 well as from consent requests made when the connection was initially being opened.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-currentroundtriptime |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: readable property - Web APIs

RTCIceCandidatePairStats: readable property
===========================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

**Non-standard:** This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.

 The *obsolete* `RTCIceCandidatePairStats`
 property **`readable`** reports whether or not the connection
 described by the candidate pair has received at least one valid incoming ICE
 request.

Value
-----

 A Boolean value which is `true` if the connection described by this
 candidate pair has received at least one valid ICE request, and is therefore ready to be
 read from.

**Note:** This property was removed from the specification in early 2017
 because you can determine whether or not the connection is readable by checking to see
 if `requestsReceived` is
 greater than 0:

```js
if (icpStats.requestsReceived > 0) {
  /\* at least one ICE request has been received \*/
}
```

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: bytesSent property - Web APIs

RTCIceCandidatePairStats: bytesSent property
============================================

 The `RTCIceCandidatePairStats` property
 **`bytesSent`** indicates the total number of payload
 bytes—that is, bytes which aren't overhead such as headers or padding—that have been
 sent so far on the connection described by the candidate pair.

 The `bytesReceived` property
 reports the number of bytes received so far on the described connection.

Syntax
------

```js
sent = rtcIceCandidatePairStats.bytesSent
```

### Value

 An integer value indicating the total number of bytes sent so far on the connection
 described by this candidate pair. Only data bytes are counted; overhead such as padding,
 headers, and the like are not included in this count.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-bytessent |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: timestamp property - Web APIs

RTCIceCandidatePairStats: timestamp property
============================================

The **`timestamp`** property of the `RTCIceCandidatePairStats` dictionary is a `DOMHighResTimeStamp` object specifying the time at which the data in the object was sampled.

Value
-----

A `DOMHighResTimeStamp` value indicating the time at which the activity described by the statistics in this object was recorded, in milliseconds elapsed since the beginning of January 1, 1970, UTC.

The value should be accurate to within a few milliseconds but may not be entirely precise, either because of hardware or operating system limitations or because of fingerprinting protection in the form of reduced clock precision or accuracy.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcstats-timestamp |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: requestsSent property - Web APIs

RTCIceCandidatePairStats: requestsSent property
===============================================

 The `RTCIceCandidatePairStats` dictionary's
 **`requestsSent`** property indicates the total number of
 STUN connectivity check requests that have been sent so far on the
 connection described by this pair of candidates.

Value
-----

 An integer value which specifies the number of STUN connectivity requests that have
 been sent to date on the connection described by this pair of ICE
 candidates.

**Note:** The reported number of requests *does not* include
 retransmissions. If a request had to be repeated due to network issues, it will be
 counted multiple times here. This differs from
 `requestsReceived`, which
 *does* include retransmissions.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-requestssent |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: transportId property - Web APIs

RTCIceCandidatePairStats: transportId property
==============================================

 The **`transportId`** property
 uniquely identifies the `RTCIceTransport` that was inspected to obtain
 the transport-related statistics contained in the
 `RTCIceCandidatePairStats` object.

Value
-----

 A string which uniquely identifies the `RTCIceTransport`
 object from which the transport-related data was obtained for the statistics contained
 in this `RTCIceCandidatePairStats` object.

 The transport-related statistics come from the `RTCTransportStats`
 dictionary's properties.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-transportid |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: writable property - Web APIs

RTCIceCandidatePairStats: writable property
===========================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

**Non-standard:** This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.

 The *obsolete* `RTCIceCandidatePairStats`
 property **`writable`** reports whether or not the connection
 described by the candidate pair is writable.

Value
-----

 A Boolean value which is `true` if the connection described by this
 candidate pair has received acknowledgement of receipt (ACK) for at least one ICE
 request *and* that STUN consent hasn't expired.

**Note:** This property was removed from the specification in early 2017
 because you can determine whether or not an incoming ICE request is available to read
 by checking to see if `responsesReceived` is greater than 0 and that the time specified by
 `consentExpiredTimestamp` has not passed:

```js
if (
  icpStats.responsesReceived > 0 &&
  icpStats.consentExpiredTimestamp < performance.now()
) {
  /\* at least one ICE response has been received \*/
}
```

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: bytesReceived property - Web APIs

RTCIceCandidatePairStats: bytesReceived property
================================================

 The `RTCIceCandidatePairStats` property
 **`bytesReceived`** indicates the total number of payload
 bytes—that is, bytes which aren't overhead such as headers or padding—that have been
 received to date on the connection described by the candidate pair.

 The `bytesSent` property reports the
 number of bytes sent so far on the described connection.

Value
-----

 An integer value indicating the total number of bytes received so far on the connection
 described by this candidate pair. Only data bytes are counted; overhead such as padding,
 headers, and the like are not included in this count.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-bytesreceived |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: id property - Web APIs

RTCIceCandidatePairStats: id property
=====================================

The **`id`** property of the `RTCIceCandidatePairStats` dictionary is a string that uniquely identifies the object for which this object provides statistics.

Using the `id`, you can correlate this statistics object with others, in order to monitor statistics over time for a given WebRTC object, such as an `RTCPeerConnection`, or an `RTCDataChannel`.

Value
-----

A string that uniquely identifies the object for which this `RTCIceCandidatePairStats` object provides statistics.

The format of the ID string is not defined by the specification, so you cannot reliably make any assumptions about the contents of the string, or assume that the format of the string will remain unchanged for a given object type.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcstats-id |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: state property - Web APIs

RTCIceCandidatePairStats: state property
========================================

The **`state`** property is a string that indicates the state of the checklist of which the candidate pair is a member.

Value
-----

A string whose value is one of the following:

`failed`

A check for this pair has been performed but failed. A failure can occur either because no response was received or because the response indicated that an unrecoverable error occurred.

`frozen`

No check has been performed yet for this candidate pair, and performing the check is blocked until another check is successful. Once that check has succeeded, this pair will unfreeze and move into the `waiting` state.

`in-progress`

A check has been initiated for this pair, but the check's transaction is still in progress.

`succeeded`

A check for this pair has been completed successfully.

`waiting`

This pair has not yet been checked, but the check can be performed as soon as this pair is the highest priority pair remaining in the `waiting` state.

ICE check lists
---------------

 During ICE negotiation, the ICE layer builds up a *checklist*, which is
 a list of potential pairings of ICE candidates. Each pair has a state, represented by a string literal.

![A diagram showing how ICE candidate pairs change state as the checklist is analyzed](/en-US/docs/Web/API/RTCIceCandidatePairStats/state/ice-check-list-states.svg)

 When a candidate pair is added to the checklist, it begins in the `frozen`
 state. As soon as there are no checks ongoing that block the pair from being analyzed,
 it is unfrozen and moves into the `waiting` state. This may happen
 immediately upon being added to the checklist.

 Each time a candidate pair is checked, the next-highest priority candidate
 pair remaining on the checklist moves from the `waiting` state to the
 `in-progress` state, and its check begins. If the check fails for any reason,
 the pair moves into its final state, `failed`. If the check succeeds, the
 pair ends up in the `succeeded` state.
 The ICE checklist state for any given pair of ICE candidates can be found in the
 corresponding `state` property.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-state |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: nominated property - Web APIs

RTCIceCandidatePairStats: nominated property
============================================

 The `RTCIceCandidatePairStats` property
 **`nominated`** specifies whether or not the candidate pair
 described by the underlying `RTCIceCandidatePair` has been nominated to be
 used as the configuration for the WebRTC connection.

Value
-----

 A Boolean value which is set to `true` by the ICE layer if the controlling
 user agent has indicated that the candidate pair should be used to configure the WebRTC
 connection between the two peers.

**Note:** If more than one candidate pair are nominated at the same
 time, the one whose priority is higher will be selected for use.

 Once a candidate pair has been nominated and the two peers have each reconfigured
 themselves to use the specified configuration, the ICE negotiation process can
 potentially end (or it can continue, to allow the connection to adapt to changing
 conditions).

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-nominated |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidateStats: localCandidateId property - Web APIs

RTCIceCandidateStats: localCandidateId property
===============================================

 The `RTCIceCandidatePairStats` property
 **`localCandidateId`** is a string that uniquely identifies
 the local ICE candidate which was analyzed to generate the
 `RTCIceCandidateStats` used to compute the statistics for this pair of
 candidates.

Value
-----

 A string giving a unique identifier for the local
 `RTCIceCandidate` for the connection described by this
 `RTCIceCandidatePairStats` object.

 This candidate is the source of one of the two `RTCIceCandidateStats`
 objects that were used to compute the contents of this
 `RTCIceCandidatePairStats` object.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-localcandidateid |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: responsesReceived property - Web APIs

RTCIceCandidatePairStats: responsesReceived property
====================================================

 The **`responsesReceived`** property
 in the `RTCIceCandidatePairStats` dictionary indicates the total number
 of STUN connectivity check responses that have been received on the
 connection described by this pair of candidates.

Value
-----

 An integer value which specifies the number of STUN connectivity request responses that
 have been received on the connection described by this pair of candidates so far.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-responsesreceived |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: responsesSent property - Web APIs

RTCIceCandidatePairStats: responsesSent property
================================================

 The `RTCIceCandidatePairStats` dictionary's
 **`responsesSent`** property indicates the total number of
 STUN connectivity check responses that have been sent so far on the
 connection described by this pair of candidates.

Value
-----

 An integer value indicating the number of times a response has been sent to a
 STUN connectivity check request.

**Note:** Since it isn't possible to tell the difference between
 connectivity check requests and consent requests, this value includes both.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-responsessent |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: totalRoundTripTime property - Web APIs

RTCIceCandidatePairStats: totalRoundTripTime property
=====================================================

 The `RTCIceCandidatePairStats` dictionary's
 **`totalRoundTripTime`** property is the total time that has
 elapsed between sending STUN requests and receiving the responses, for
 all such requests that have been made so far on the pair of candidates described by
 this `RTCIceCandidatePairStats` object. This value includes both
 connectivity check and consent check requests.

Value
-----

 This floating-point value indicates the total number of seconds which have elapsed
 between sending out STUN connectivity and consent check requests and receiving their
 responses, for all such requests made so far on the connection described by this
 candidate pair.

 You can calculate the average round-trip time (RTT) by dividing this value by the value
 of the `responsesReceived`
 property:

```js
rtt =
  rtcIceCandidatePairStats.totalRoundTripTime /
  rtcIceCandidatePairStats.responsesReceived;
```

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-totalroundtriptime |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: type property - Web APIs

RTCIceCandidatePairStats: type property
=======================================

The **`type`** property of the `RTCIceCandidatePairStats` dictionary is a string with the value `"candidate-pair"`.

 Different statistics are obtained by iterating the `RTCStatsReport` object returned by a call to `RTCPeerConnection.getStats()`.
 The type indicates the set of statistics available through the object in a particular iteration step.
 A value of `"candidate-pair"` indicates that the statistics available in the current step are those defined in `RTCIceCandidatePairStats`.

Value
-----

A string with the value `"candidate-pair"`.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcstats-type |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: remoteCandidateId property - Web APIs

RTCIceCandidatePairStats: remoteCandidateId property
====================================================

 The `RTCIceCandidatePairStats` property
 **`remoteCandidateId`** is a string that uniquely identifies
 the remote ICE candidate which was analyzed to generate the
 `RTCIceCandidateStats` used to compute the statistics for this pair of
 candidates.

Value
-----

 A string uniquely identifies the remote ICE
 candidate—that is, the candidate describing a configuration for the remote peer—which is
 represented by the remote end of these statistics.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-remotecandidateid |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: availableIncomingBitrate property - Web APIs

RTCIceCandidatePairStats: availableIncomingBitrate property
===========================================================

 The `RTCIceCandidatePairStats` property
 **`availableIncomingBitrate`** returns a value indicative of
 the available inbound capacity of the network connection represented by the candidate
 pair. The higher the value, the more bandwidth you can assume is available for
 incoming data.

 You can get the outgoing available bitrate from
 `availableoutgoingBitrate`.

Syntax
------

```js
availableIncomingBitrate = rtcIceCandidatePairStats.availableIncomingBitrate
```

### Value

 A floating-point value which approximates the amount of available bandwidth for
 incoming data on the network connection described by the
 `RTCIceCandidatePair`. The value is reported in bits per second and is
 computed over a 1-second interval.

 The value returned is calculated by adding up the available bit rate for every
 RTP stream using the connection described by this candidate pair. The
 returned value doesn't take into account overhead introduced by underlying protocols,
 including IP, UDP, or TCP.

**Note:** The returned value is computed using a method similar—but not
 identical—to the Transport Independent Application Specific Maximum (TIAS) described
 in RFC 3890, section 6.2.

Specifications
--------------

#### No specification found

No specification data found for `api.RTCIceCandidatePairStats.availableIncomingBitrate`.
Check for problems with this page or contribute a missing `spec_url` to mdn/browser-compat-data. Also make sure the specification is included in w3c/browser-specs.

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: requestsReceived property - Web APIs

RTCIceCandidatePairStats: requestsReceived property
===================================================

 The `RTCIceCandidatePairStats` dictionary's
 **`requestsReceived`** property indicates the total number of
 STUN connectivity check requests that have been received so far on the
 connection described by this pairing of candidates.

Value
-----

 An integer value which specifies the number of STUN connectivity and/or consent
 requests that have been received to date on the connection described by this pair of
 ICE candidates.

 Because there's no way to tell the difference between requests made to check
 connectivity and requests made to check consent, the returned figure includes both.

**Note:** The reported number of requests includes retransmissions. If a
 request had to be repeated due to network issues, it will be counted multiple times
 here. This differs from `requestsSent`, which *does not* include retransmisions.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-requestsreceived |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidateStats: lastPacketSentTimestamp property - Web APIs

RTCIceCandidateStats: lastPacketSentTimestamp property
======================================================

 The `RTCIceCandidatePairStats` property
 **`lastPacketSentTimestamp`** indicates the time at which the
 connection described by the candidate pair last sent a packet, not including
 STUN packets.

Value
-----

 A `DOMHighResTimeStamp` object indicating the timestamp at which the
 connection described by pair of candidates last sent a packet, STUN packets excluded.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-lastpacketsenttimestamp |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: priority property - Web APIs

RTCIceCandidatePairStats: priority property
===========================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

 The *obsolete* `RTCIceCandidatePairStats`
 property **`priority`** reports the priority of the candidate
 pair as an integer value. The higher the value, the more likely the WebRTC
 layer is to select the candidate pair when the time comes to establish (or re-establish)
 a connection between the two peers.

Value
-----

 An integer value indicating the priority of this pair of candidates as compared to
 other pairs on the same peer connection. The higher this value, the better the WebRTC
 layer thinks this pair of candidates is compared to the others, and the more likely a
 pair is to be selected for use.

**Note:** This property was removed from the specification as its value
 cannot be guaranteed to be accurately represented in a JavaScript number. You can
 calculate its value using the algorithm described in RFC 5245, section 5.7.2 if
 you need this information and can accept the risk that the result may not be entirely
 accurate.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-priority |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


RTCIceCandidatePairStats: lastPacketReceivedTimestamp property - Web APIs

RTCIceCandidatePairStats: lastPacketReceivedTimestamp property
==============================================================

 The `RTCIceCandidatePairStats` property
 **`lastPacketReceivedTimestamp`** indicates the time at which
 the connection described by the candidate pair last received a packet.
 STUN packets are not included.

Value
-----

 A `DOMHighResTimeStamp` object indicating the timestamp at which the
 connection described by pair of candidates last received a packet, STUN packets
 excluded.

Specifications
--------------

| Specification |
| --- |
| Identifiers for WebRTC's Statistics API # dom-rtcicecandidatepairstats-lastpacketreceivedtimestamp |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


WritableStream: abort() method - Web APIs

WritableStream: abort() method
==============================

The **`abort()`** method of the `WritableStream` interface aborts the stream, signaling that the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.

Syntax
------

```js
abort(reason)
```

### Parameters

`reason`

A string providing a human-readable reason for the abort.

### Return value

A `Promise`, which fulfills with the value given in the `reason` parameter.

### Exceptions

`TypeError`

The stream you are trying to abort is not a `WritableStream`, or it is locked.

Examples
--------

```js
const writableStream = new WritableStream(
  {
    write(chunk) {
      // ...
    },
    close() {
      // ...
    },
    abort(err) {
      // ...
    },
  },
  queuingStrategy,
);

// ...

// abort the stream later on, when required
writableStream.abort();
```

Specifications
--------------

| Specification |
| --- |
| Streams Standard # ref-for-ws-abort③ |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


WritableStream: WritableStream() constructor - Web APIs

WritableStream: WritableStream() constructor
============================================

 The **`WritableStream()`** constructor creates
 a new `WritableStream` object instance.

Syntax
------

```js
new WritableStream(underlyingSink)
new WritableStream(underlyingSink, queuingStrategy)
```

### Parameters

`underlyingSink` Optional

 An object containing methods and properties that define how the constructed stream
 instance will behave. The `controller` parameter passed to this object's methods is a `WritableStreamDefaultController` that provides abort and error signaling. `underlyingSink` can contain the following:

`start(controller)` Optional

 This is a method, called immediately when the object is constructed. The
 contents of this method are defined by the developer, and should aim to get access
 to the underlying sink. If this process is to be done asynchronously, it can
 return a promise to signal success or failure.

`write(chunk, controller)` Optional

 This method, also defined by the developer, will be called when a new chunk of
 data (specified in the `chunk` parameter) is ready to be written to the
 underlying sink. It can return a promise to signal success or failure of the write
 operation. This method will
 be called only after previous writes have succeeded, and never after the stream is
 closed or aborted (see below).

`close(controller)` Optional

 This method, also defined by the developer, will be called if the app signals
 that it has finished writing chunks to the stream. The contents should do whatever
 is necessary to finalize writes to the underlying sink, and release access to it.
 If this process is asynchronous, it can return a promise to signal success or
 failure. This method will be called only after all queued-up writes have
 succeeded.

`abort(reason)` Optional

 This method, also defined by the developer, will be called if the app signals
 that it wishes to abruptly close the stream and put it in an errored state. It can
 clean up any held resources, much like `close()`, but
 `abort()` will be called even if writes are queued up — those chunks
 will be thrown away. If this process is asynchronous, it can return a promise to
 signal success or failure. The `reason` parameter contains a
 string describing why the stream was aborted.

`queuingStrategy` Optional

 An object that optionally defines a queuing strategy for the stream. This takes two
 parameters:

`highWaterMark`

 A non-negative integer — this defines the total number of chunks that can be
 contained in the internal queue before backpressure is applied.

`size(chunk)`

A method containing a parameter `chunk` — this indicates the size to use for each chunk, in bytes.

**Note:** You could define your own custom
 `queuingStrategy`, or use an instance of
 `ByteLengthQueuingStrategy` or `CountQueuingStrategy`
 for this object value. If no `queuingStrategy` is supplied, the default
 used is the same as a `CountQueuingStrategy` with a high water mark of 1.

### Return value

An instance of the `WritableStream` object.

Examples
--------

 The following example illustrates several features of this interface. It shows the
 creation of the `WritableStream` with a custom sink and an API-supplied
 queuing strategy. It then calls a function called `sendMessage()`, passing
 the newly created stream and a string. Inside this function it calls the stream's
 `getWriter()` method, which returns an instance of
 `WritableStreamDefaultWriter`. A `forEach()` call is used to
 write each chunk of the string to the stream. Finally, `write()` and
 `close()` return promises that are processed to deal with success or failure
 of chunks and streams.

```js
const list = document.querySelector("ul");

function sendMessage(message, writableStream) {
  // defaultWriter is of type WritableStreamDefaultWriter
  const defaultWriter = writableStream.getWriter();
  const encoder = new TextEncoder();
  const encoded = encoder.encode(message, { stream: true });
  encoded.forEach((chunk) => {
    defaultWriter.ready
      .then(() => defaultWriter.write(chunk))
      .then(() => {
        console.log("Chunk written to sink.");
      })
      .catch((err) => {
        console.log("Chunk error:", err);
      });
  });
  // Call ready again to ensure that all chunks are written
  // before closing the writer.
  defaultWriter.ready
    .then(() => {
      defaultWriter.close();
    })
    .then(() => {
      console.log("All chunks written");
    })
    .catch((err) => {
      console.log("Stream error:", err);
    });
}

const decoder = new TextDecoder("utf-8");
const queuingStrategy = new CountQueuingStrategy({ highWaterMark: 1 });
let result = "";
const writableStream = new WritableStream(
  {
    // Implement the sink
    write(chunk) {
      return new Promise((resolve, reject) => {
        const buffer = new ArrayBuffer(1);
        const view = new Uint8Array(buffer);
        view[0] = chunk;
        const decoded = decoder.decode(view, { stream: true });
        const listItem = document.createElement("li");
        listItem.textContent = `Chunk decoded: ${decoded}`;
        list.appendChild(listItem);
        result += decoded;
        resolve();
      });
    },
    close() {
      const listItem = document.createElement("li");
      listItem.textContent = `[MESSAGE RECEIVED] ${result}`;
      list.appendChild(listItem);
    },
    abort(err) {
      console.log("Sink error:", err);
    },
  },
  queuingStrategy,
);

sendMessage("Hello, world.", writableStream);
```

You can find the full code in our Simple writer example.

### Backpressure

 Because of how backpressure is supported in the API, its implementation in code may be
 less than obvious. To see how backpressure is implemented look for three things.

* The `highWaterMark` property, which is set when creating the counting
 strategy (line 35), sets the maximum amount of data that the
 `WritableStream` instance will handle in a single `write()`
 operation. In this example, it's the maximum amount of data that can be sent to
 `defaultWriter.write()` (line 11).
* The `defaultWriter.ready` property returns a promise that resolves when
 the sink (the first property of the `WritableStream` constructor) is done
 writing data. The data source can either write more data (line 11) or call
 `close()` (line 24). Calling `close()` too early can prevent
 data from being written. This is why the example calls
 `defaultWriter.ready` twice (lines 9 and 22).
* The `Promise` returned by the sink's `write()` method (line
 40) tells the `WritableStream` and its writer when to resolve
 `defaultWriter.ready`.
Specifications
--------------

| Specification |
| --- |
| Streams Standard # ref-for-ws-constructor④ |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


WritableStream: getWriter() method - Web APIs

WritableStream: getWriter() method
==================================

 The **`getWriter()`** method of the `WritableStream` interface returns a new instance of `WritableStreamDefaultWriter` and locks the stream to that instance.
 While the stream is locked, no other writer can be acquired until this one is released.

Syntax
------

```js
getWriter()
```

### Parameters

None.

### Return value

A `WritableStreamDefaultWriter` object instance.

### Exceptions

`TypeError`

The stream you are trying to create a writer for is not a `WritableStream` or it is already locked to another writer.

Examples
--------

 The following example illustrates several features of this interface.
 It shows the creation of the `WritableStream` with a custom sink and an API-supplied queuing strategy.
 t then calls a function called `sendMessage()`, passing the newly created stream and a string. Inside this function it calls the stream's `getWriter()` method, which returns an instance of `WritableStreamDefaultWriter`.
 A `forEach()` call is used to write each chunk of the string to the stream. Finally, `write()` and `close()` return promises that are processed to deal with success or failure of chunks and streams.

```js
const list = document.querySelector("ul");

function sendMessage(message, writableStream) {
  // defaultWriter is of type WritableStreamDefaultWriter
  const defaultWriter = writableStream.getWriter();
  const encoder = new TextEncoder();
  const encoded = encoder.encode(message, { stream: true });
  encoded.forEach((chunk) => {
    defaultWriter.ready
      .then(() => defaultWriter.write(chunk))
      .then(() => {
        console.log("Chunk written to sink.");
      })
      .catch((err) => {
        console.log("Chunk error:", err);
      });
  });
  // Call ready again to ensure that all chunks are written
  // before closing the writer.
  defaultWriter.ready
    .then(() => {
      defaultWriter.close();
    })
    .then(() => {
      console.log("All chunks written");
    })
    .catch((err) => {
      console.log("Stream error:", err);
    });
}

const decoder = new TextDecoder("utf-8");
const queuingStrategy = new CountQueuingStrategy({ highWaterMark: 1 });
let result = "";
const writableStream = new WritableStream(
  {
    // Implement the sink
    write(chunk) {
      return new Promise((resolve, reject) => {
        const buffer = new ArrayBuffer(1);
        const view = new Uint8Array(buffer);
        view[0] = chunk;
        const decoded = decoder.decode(view, { stream: true });
        const listItem = document.createElement("li");
        listItem.textContent = `Chunk decoded: ${decoded}`;
        list.appendChild(listItem);
        result += decoded;
        resolve();
      });
    },
    close() {
      const listItem = document.createElement("li");
      listItem.textContent = `[MESSAGE RECEIVED] ${result}`;
      list.appendChild(listItem);
    },
    abort(err) {
      console.log("Sink error:", err);
    },
  },
  queuingStrategy,
);

sendMessage("Hello, world.", writableStream);
```

You can find the full code in our Simple writer example.

Specifications
--------------

| Specification |
| --- |
| Streams Standard # ref-for-ws-get-writer① |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


WritableStream: close() method - Web APIs

WritableStream: close() method
==============================

The **`close()`** method of the `WritableStream` interface closes the associated stream. All chunks written before this method is called are sent before the returned promise is fulfilled.

This is equivalent to getting a `WritableStreamDefaultWriter` with `getWriter()`, calling `close()` on it.

Syntax
------

```js
close()
```

### Parameters

None.

### Return value

 A `Promise` which fulfills with the `undefined` when all
 remaining chunks were successfully written before the close, or rejects with an error if
 a problem was encountered during the process.

### Exceptions

`TypeError`

The stream you are trying to close is locked.

Examples
--------

 The following example illustrates several features of the `WritableStream`. It shows the
 creation of the `WritableStream` with a custom sink and an API-supplied
 queuing strategy. It then calls a function called `sendMessage()`, passing
 the newly created stream and a string. Inside this function it calls the stream's
 `getWriter()` method, which returns an instance of
 `WritableStreamDefaultWriter`. A `forEach()` call is used to
 write each chunk of the string to the stream. Finally, `write()` and
 `close()` return promises that are processed to deal with success or failure
 of chunks and streams. Note that in order to be able to call `close()` on the stream itself,
 the writer must be disconnected using `defaultWriter.releaseLock();`.
```

<ul id="log"></ul>
```

```
const list = document.getElementById("log");
function log(message) {
  const listItem = document.createElement("li");
  listItem.textContent = message;
  list.appendChild(listItem);
}
```

```js
function sendMessage(message, writableStream) {
  // defaultWriter is of type WritableStreamDefaultWriter
  const defaultWriter = writableStream.getWriter();
  const encoder = new TextEncoder();
  const encoded = encoder.encode(message, { stream: true });
  encoded.forEach((chunk) => {
    defaultWriter.ready
      .then(() => {
        defaultWriter.write(chunk);
      })
      .catch((err) => {
        log("Chunk error:", err);
      });
  });

  // Call ready again to ensure that all chunks are written
  // before closing the writer.
  defaultWriter.ready
    .then(() => {
      defaultWriter.releaseLock();
      writableStream.close();
    })
    .then(() => {
      log("All chunks written / stream closed.");
    })
    .catch((err) => {
      log("Stream error:", err);
    });
}

const decoder = new TextDecoder("utf-8");
const queuingStrategy = new CountQueuingStrategy({ highWaterMark: 1 });
let result = "";
const writableStream = new WritableStream(
  {
    // Implement the sink
    write(chunk) {
      return new Promise((resolve, reject) => {
        const buffer = new ArrayBuffer(1);
        const view = new Uint8Array(buffer);
        view[0] = chunk;
        const decoded = decoder.decode(view, { stream: true });
        const listItem = document.createElement("li");
        result += decoded;
        resolve();
      });
    },
    close() {
      const listItem = document.createElement("li");
      log(`[MESSAGE RECEIVED] ${result}`);
    },
    abort(err) {
      log("Sink error:", err);
    },
  },
  queuingStrategy,
);

log("Sending 'Hello, world.' message.");
sendMessage("Hello, world.", writableStream);
```

Specifications
--------------

| Specification |
| --- |
| Streams Standard # ref-for-ws-close① |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.