From 0440e770d1423973eac9fe9840438c09f13e5554 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 22 Aug 2024 09:48:23 +0100 Subject: [PATCH 01/16] Document WebSocketStream --- .../using_websocketstream/index.md | 270 ++++++++++++++++++ files/jsondata/GroupData.json | 10 +- 2 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 files/en-us/web/api/websockets_api/using_websocketstream/index.md diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md new file mode 100644 index 000000000000000..4988b8ab86d0b11 --- /dev/null +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -0,0 +1,270 @@ +--- +title: Using WebSocketStream to write a client +slug: Web/API/WebSockets_API/Using_WebSocketStream +page-type: guide +status: + - non-standard +--- + +{{DefaultAPISidebar("WebSockets API")}}{{non-standard_header}} + +The {{domxref("WebSocketStream")}} API is a modern reimagining of WebSocket client-side JavaScript functionality. It is promise-based, and therefore simpler to work with than the traditional {{domxref("WebSocket")}} API in the modern JavaScript scosystem. In addition, it uses the [Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. + +This article explains how to use the {{domxref("WebSocketStream")}} API to create a simple WebSocket client. + +> [!NOTE] +> Backpressure is an issue with the traditional `WebSocket` API — it has no way to apply backpressure, therefore when messages arrive faster than the application can process them, the application will either fill up the device's memory by buffering those messages, become unresponsive due to 100% CPU usage, or both. + +## Feature detection + +To check whether the `WebSocketStream` API is supported, you can use the following: + +```js +if ("WebSocketStream" in window) { + // WebSocketStream is supported +} +``` + +## Creating a WebSocketStream object + +To create a WebSocket client, you first need to create a new `WebSocketStream` instance using the {{domxref("WebSocketStream.WebSocketStream", "WebSocketStream()")}} constructor. In its simplest form, it takes the URL of the WebSocket server as an argument: + +```js +const wss = new WebSocketStream("example.com/wss"); +``` + +It can also take an options object containing custom protocols and/or an {{domxref("AbortSignal")}} (see [Closing the connection](#closing_the_connection)): + +```js +const controller = new AbortController(); +const chatWSS = new WebSocketStream("example.com/chat-wss", { + protocols: ["chat", "chatv2"], + signal: controller.signal, +}); +``` + +## Sending and receiving data + +The `WebSocketStream` instance has an {{domxref("WebSocketStream.opened", "opened")}} property — this returns a promise that fulfills with an object containing a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance once the WebSocket connection is opened successfully: + +```js +const { readable, writable } = await wss.opened; +``` + +Calling {{domxref("ReadableStream.getReader", "getReader()")}} and {{domxref("WritableStream.getWriter", "getWriter()")}} on these objects provides us with a {{domxref("ReadableStreamDefaultReader")}} and a {{domxref("WritableStreamDefaultWriter")}} respectively, which can be used to read from and write to the socket connection: + +```js +const reader = readable.getReader(); +const writer = writable.getWriter(); +``` + +To write data to the socket, you can use {{domxref("WritableStreamDefaultWriter.write()")}}: + +```js +await writer.write("My message"); +``` + +To read data from the socket, you can continuously call {{domxref("ReadableStreamDefaultReader.read()")}} until the stream is done (i.e. until the returned `value` is `undefined`, and `done` is `true`): + +```js +while (true) { + const { value, done } = await reader.read(); + if (done) { + break; + } + + // Process value in some way +} +``` + +## Closing the connection + +With `WebSocketStream`, the information previously available via the `WebSocket` {{domxref("WebSocket.close_event", "close")}} and {{domxref("WebSocket.error_event", "error")}} events is now available via the {{domxref("WebSocketStream.closed", "closed")}} property — this returns a promise that fulfills with an object containing the closing code (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)) and reason sent by the server: + +```js +const { code, reason } = await wss.closed; +``` + +The promise rejects in the event of an unclean close. + +As mentioned earlier, the WebSocket connection can be closed using an {{domxref("AbortController")}}. The necessary {{domxref("AbortSignal")}} is passed to the `WebSocketStream` constructor during creation, and {{domxref("AbortController.abort()")}} can then be called when required: + +```js +const controller = new AbortController(); +const wss = new WebSocketStream("example.com/wss", { + signal: controller.signal, +}); + +// some time later + +controller.abort(); +``` + +Alternatively you can use the {{domxref("WebSocketStream.close()")}} method to close a connection, however this is mainly needed if you wish to specify a custom code and reason for the server to report: + +```js +wss.close({ + code: 4000, + reason: "Night draws to a close", +}); +``` + +> [!NOTE] +> Depending on the server setup and status code you use, the server may choose to ignore a custom code in favor of a standard code that is correct for the closing reason. + +## A complete sample client + +To demonstrate basic usage of `WebSocketStream`, we've created a sample client. You can see the [full listing](#full_listing) at the bottom of the article, and follow along with the explanation below. + +> [!NOTE] +> To get the example working, you'll also need a server component. We wrote our client to work along with the Deno server explained in [Writing a WebSocket server in JavaScript (Deno)](/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno), but any compatible server will do. + +First we grab a reference to a DOM element into which we will write output messages, and define a utility function that writes a message to it: + +```js +const output = document.querySelector("#output"); + +function writeToScreen(message) { + output.insertAdjacentHTML("beforeend", `

${message}

`); +} +``` + +Next, we create an `if ... else` structure to feature detect `WebSocketStream` and output an informative message on non-supporting browsers: + +```js +if ("WebSocketStream" in window) { + // supporting code path +} else { + writeToScreen("Your browser does not support WebSocketStream"); +} +``` + +In the supporting code path, we begin by defining a variable containing the WebSocket server URL, and constructing a new `WebSocketServer` instance: + +```js +const wsURL = "ws://127.0.0.1/"; +const wss = new WebSocketStream(wsURL); +``` + +The main bulk of our code is contained within the `start()` function, which we define and then immediately invoke. We await the {{domxref("WebSocketStream.opened", "opened")}} promise, then after it fulfills write a message to let the reader know the connection is successful and create {{domxref("ReadableStreamDefaultReader")}} and {{domxref("WritableStreamDefaultWriter")}} instances from the returned `readable` and `writable` properties. + +Next, we `write()` a `"ping"` value to the socket and communicate that to the user. At this point, the server will respond with a `"pong"` message. We await the `read()` of the read message, communicate it to the user, then write another `"ping"` to the server after a timeout of 5 seconds. This continues the `"ping"`/`"pong"` loop indefinitely. + +```js +async function start() { + const { readable, writable } = await wss.opened; + writeToScreen("CONNECTED"); + const reader = readable.getReader(); + const writer = writable.getWriter(); + + await writer.write("ping"); + writeToScreen("SENT: ping"); + + while (true) { + const { value, done } = await reader.read(); + writeToScreen(`RECEIVED: ${value}`); + if (done) { + break; + } + + setTimeout(async () => { + await writer.write("ping"); + writeToScreen("SENT: ping"); + }, 5000); + } +} + +start(); +``` + +Finally, we include a promise-style code section to inform the user of the code and reason if the WebSocket connection is closed for any reason, as signalled by the {{domxref("WebSocketStream.closed", "closed")}} promise fulfilling: + +```js +wss.closed.then((result) => { + writeToScreen( + `DISCONNECTED: code ${result.closeCode}, message "${result.reason}"`, + ); + console.error("Socket closed", result.closeCode, result.reason); +}); +``` + +You can test this out by including something like the following after the previous code block: + +```js +setTimeout(async () => { + wss.close({ + code: 1000, + reason: "That's all folks", + }); +}, 10000); +``` + +### Full listing + +```html + + + + + WebSocketStream Test + + +

WebSocketStream Test

+

Sends a ping every five seconds

+
+ + + +``` diff --git a/files/jsondata/GroupData.json b/files/jsondata/GroupData.json index ddf6698c0075631..cb78f8d6c383221 100644 --- a/files/jsondata/GroupData.json +++ b/files/jsondata/GroupData.json @@ -2240,9 +2240,15 @@ "/docs/Web/API/WebSockets_API/Writing_WebSocket_servers", "/docs/Web/API/WebSockets_API/Writing_WebSocket_server", "/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_Java", - "/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno" + "/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno", + "/docs/Web/API/WebSockets_API/Using_WebSocketStream" + ], + "interfaces": [ + "WebSocket", + "WebSocketStream", + "CloseEvent", + "MessageEvent" ], - "interfaces": ["WebSocket", "CloseEvent", "MessageEvent"], "methods": [], "properties": [], "events": [] From fb8a621d044135a9739dda6321d4241cc268d0f2 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Fri, 23 Aug 2024 08:18:11 +0100 Subject: [PATCH 02/16] Add WebSocket interface documentation --- files/en-us/web/api/websockets_api/index.md | 20 ++-- .../using_websocketstream/index.md | 2 +- .../web/api/websocketstream/close/index.md | 71 +++++++++++++++ .../web/api/websocketstream/closed/index.md | 88 ++++++++++++++++++ files/en-us/web/api/websocketstream/index.md | 91 +++++++++++++++++++ .../web/api/websocketstream/opened/index.md | 87 ++++++++++++++++++ .../web/api/websocketstream/url/index.md | 39 ++++++++ .../websocketstream/websocketstream/index.md | 77 ++++++++++++++++ 8 files changed, 467 insertions(+), 8 deletions(-) create mode 100644 files/en-us/web/api/websocketstream/close/index.md create mode 100644 files/en-us/web/api/websocketstream/closed/index.md create mode 100644 files/en-us/web/api/websocketstream/index.md create mode 100644 files/en-us/web/api/websocketstream/opened/index.md create mode 100644 files/en-us/web/api/websocketstream/url/index.md create mode 100644 files/en-us/web/api/websocketstream/websocketstream/index.md diff --git a/files/en-us/web/api/websockets_api/index.md b/files/en-us/web/api/websockets_api/index.md index e74539a819187ac..894e1946e670cbe 100644 --- a/files/en-us/web/api/websockets_api/index.md +++ b/files/en-us/web/api/websockets_api/index.md @@ -2,7 +2,9 @@ title: The WebSocket API (WebSockets) slug: Web/API/WebSockets_API page-type: web-api-overview -browser-compat: api.WebSocket +browser-compat: + - api.WebSocket + - api.WebSocketStream --- {{DefaultAPISidebar("WebSockets API")}} @@ -12,10 +14,16 @@ The **WebSocket API** is an advanced technology that makes it possible to open a > [!NOTE] > While a WebSocket connection is functionally somewhat similar to standard Unix-style sockets, they are not related. +### WebSocketStream + +The {{domxref("WebSocketStream")}} API is a modern reimagining of WebSocket client-side JavaScript functionality. It is promise-based, and therefore simpler to work with than the traditional {{domxref("WebSocket")}} API in the modern JavaScript scosystem. In addition, it uses the [Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for more information. + ## Interfaces - [`WebSocket`](/en-US/docs/Web/API/WebSocket) - : The primary interface for connecting to a WebSocket server and then sending and receiving data on the connection. +- [`WebSocketStream`](/en-US/docs/Web/API/WebSocketStream) {{non-standard_inline}} + - : Promise-based interface for connecting to a WebSocket server; uses [streams](/en-US/docs/Web/API/Streams_API) to send and receive data on the connection. - [`CloseEvent`](/en-US/docs/Web/API/CloseEvent) - : The event sent by the WebSocket object when the connection closes. - [`MessageEvent`](/en-US/docs/Web/API/MessageEvent) @@ -28,28 +36,26 @@ The **WebSocket API** is an advanced technology that makes it possible to open a - [Writing a WebSocket server in C#](/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_server) - [Writing a WebSocket server in Java](/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_Java) - [Writing a WebSocket server in JavaScript (Deno)](/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno) +- [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) ## Tools - [AsyncAPI](https://www.asyncapi.com/): A specification for describing event-driven architectures based on protocols like WebSocket. You can use it to describe WebSocket-based APIs just as you would describe REST APIs with the OpenAPI specification. Learn [why you should consider using AsyncAPI with WebSocket](https://www.asyncapi.com/blog/websocket-part1) and [how to do so](https://www.asyncapi.com/blog/websocket-part2). -- [HumbleNet](https://hacks.mozilla.org/2017/06/introducing-humblenet-a-cross-platform-networking-library-that-works-in-the-browser/): A cross-platform networking library that works in the browser. It consists of a C wrapper around WebSockets and WebRTC that abstracts away cross-browser differences, facilitating the creation of multi-user networking functionality for games and other apps. - [µWebSockets](https://github.com/uNetworking/uWebSockets): Highly scalable WebSocket server and client implementation for [C++11](https://isocpp.org/) and [Node.js](https://nodejs.org/). - [Socket.IO](https://socket.io/): A long polling/WebSocket based third party transfer protocol for [Node.js](https://nodejs.org/). - [SocketCluster](https://socketcluster.io/): A pub/sub WebSocket framework for [Node.js](https://nodejs.org/) with a focus on scalability. - [WebSocket-Node](https://github.com/theturtle32/WebSocket-Node): A WebSocket server API implementation for [Node.js](https://nodejs.org/). - [Total.js](https://www.totaljs.com/): Web application framework for [Node.js](https://nodejs.org/en) (Example: [WebSocket chat](https://github.com/totaljs/examples/tree/master/websocket)) -- [Faye](https://www.npmjs.com/package/faye-websocket): A {{DOMxRef("WebSocket")}} (two-ways connections) and [EventSource](/en-US/docs/Web/API/EventSource) (one-way connections) for [Node.js](https://nodejs.org/) Server and Client. - [SignalR](https://dotnet.microsoft.com/en-us/apps/aspnet/signalr): SignalR will use WebSockets under the covers when it's available, and gracefully fallback to other techniques and technologies when it isn't, while your application code stays the same. - [Caddy](https://caddyserver.com/): A web server capable of proxying arbitrary commands (stdin/stdout) as a websocket. - [ws](https://github.com/websockets/ws): a popular WebSocket client & server library for [Node.js](https://nodejs.org/en). -- [jsonrpc-bidirectional](https://github.com/bigstepinc/jsonrpc-bidirectional): Asynchronous RPC which, on a single connection, may have functions exported on the server and, and the same time, on the client (client may call server, server may also call client). - [cowboy](https://github.com/ninenines/cowboy): Cowboy is a small, fast and modern HTTP server for Erlang/OTP with WebSocket support. - [ZeroMQ](https://zeromq.org/): ZeroMQ is embeddable networking library that carries messages across in-process, IPC, TCP, UDP, TIPC, multicast and WebSocket. - [WebSocket King](https://websocketking.com/): A client tool to help develop, test and work with WebSocket servers. - [PHP WebSocket Server](https://github.com/napengam/phpWebSocketServer): Server written in PHP to handle connections via websockets `wss://` or `ws://` and normal sockets over `ssl://`, `tcp://` -- [Channels](https://channels.readthedocs.io/en/stable/index.html): Django library that adds support for WebSockets (and other protocols that require long running asynchronous connections). -- [Channels](https://hexdocs.pm/phoenix/channels.html): Scalable real-time communication using WebSocket in Elixir Phoenix framework. -- [LiveView](https://github.com/phoenixframework/phoenix_live_view): Real-time interactive web experiences through WebSocket in Elixir Phoenix framework. +- [Django Channels](https://channels.readthedocs.io/en/stable/index.html): Django library that adds support for WebSockets (and other protocols that require long running asynchronous connections). +- [(Phoenix) Channels](https://hexdocs.pm/phoenix/channels.html): Scalable real-time communication using WebSocket in Elixir Phoenix framework. +- [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view): Real-time interactive web experiences through WebSocket in Elixir Phoenix framework. - [Flask-SocketIO](https://flask-socketio.readthedocs.io/en/latest/): gives Flask applications access to low latency bi-directional communications between the clients and the server. - [Gorilla WebSocket](https://pkg.go.dev/github.com/gorilla/websocket): Gorilla WebSocket is a [Go](https://go.dev/) implementation of the WebSocket protocol. diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index 4988b8ab86d0b11..0fac4477b2b1567 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -110,7 +110,7 @@ wss.close({ ``` > [!NOTE] -> Depending on the server setup and status code you use, the server may choose to ignore a custom code in favor of a standard code that is correct for the closing reason. +> Depending on the server setup and status code you use, the server may choose to ignore a custom code in favor of a valid code that is correct for the closing reason. ## A complete sample client diff --git a/files/en-us/web/api/websocketstream/close/index.md b/files/en-us/web/api/websocketstream/close/index.md new file mode 100644 index 000000000000000..d7a7a114af16bfa --- /dev/null +++ b/files/en-us/web/api/websocketstream/close/index.md @@ -0,0 +1,71 @@ +--- +title: "WebSocketStream: close() method" +short-title: close() +slug: Web/API/WebSocketStream/close +page-type: web-api-instance-method +status: + - non-standard +browser-compat: api.WebSocketStream.close +--- + +{{APIRef("WebSockets API")}}{{non-standard_header}} + +The **`close()`** method of the +{{domxref("WebSocketStream")}} interface closes the WebSocket connection. The method optionally accepts an object specifying a custom code and reason for the server to report. + +> [!NOTE] +> An alternative mechanism for closing a `WebSocketStream` is to specify an {{domxref("AbortSignal")}} in the [`signal`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#signal) option of the constructor upon creation. The associated {{domxref("AbortController")}} can then be used to close the WebSocket connection. This is generally the preferred mechanism, however `close()` can be used if you wish to specify a custom code and reason for the server to report. + +## Syntax + +```js-nolint +close() +close(options) +``` + +### Parameters + +- `options` {{optional_inline}} + - : An options object containing the following properties: + - `closeCode` {{optional_inline}} + - : A number representing the closing code sent by the server (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)). + - `reason` {{optional_inline}} + - : A string representing a human-readable description of the reason why the socket connection is closed. The maximum allowed length for a `reason` string is 123 bytes. The string is automatically encoded as UTF-8 when the function is invoked. + +> [!NOTE] +> Depending on the server setup and status code you use, the server may choose to ignore a custom code in favor of a valid code that is correct for the closing reason. Valid codes are 1000, and any code within the range 3000 to 4999, inclusive. + +### Return value + +None (`undefined`). + +### Exceptions + +- `SyntaxError` {{domxref("DOMException")}} + - : Thrown if the provided `reason` is longer than the maximum allowed length of 123 bytes. + +## Examples + +```js +const wsURL = "ws://127.0.0.1/"; +const wss = new WebSocketStream(wsURL); + +setTimeout(() => { + wss.close({ + code: 1000, + reason: "That's all folks", + }); +}, 10000); +``` + +## Specifications + +Not currently a part of any specification. See https://github.com/whatwg/websockets/pull/48 for standardization progress. + +## Browser compatibility + +{{Compat}} + +## See also + +- [WebSocketStream: integrating streams with the WebSocket API](https://developer.chrome.com/docs/capabilities/web-apis/websocketstream), developer.chrome.com (2020) diff --git a/files/en-us/web/api/websocketstream/closed/index.md b/files/en-us/web/api/websocketstream/closed/index.md new file mode 100644 index 000000000000000..b4d6fcc270dd1fa --- /dev/null +++ b/files/en-us/web/api/websocketstream/closed/index.md @@ -0,0 +1,88 @@ +--- +title: "WebSocketStream: closed property" +short-title: closed +slug: Web/API/WebSocketStream/closed +page-type: web-api-instance-property +status: + - non-standard +browser-compat: api.WebSocketStream.closed +--- + +{{APIRef("WebSockets API")}}{{non-standard_header}} + +The **`closed`** read-only property of the +{{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason sent by the server. + +## Value + +A promise, which fulfills with an object containing the following properties: + +- `closeCode` + - : A number representing the closing code sent by the server (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)). +- `reason` + - : A string representing a human-readable description of the reason why the socket connection is closed. + +## Examples + +```js +const output = document.querySelector("#output"); + +function writeToScreen(message) { + output.insertAdjacentHTML("beforeend", `

${message}

`); +} + +if ("WebSocketStream" in window) { + const wsURL = "ws://127.0.0.1/"; + const wss = new WebSocketStream(wsURL); + + console.log(wss.url); + + async function start() { + const { readable, writable } = await wss.opened; + writeToScreen("CONNECTED"); + const reader = readable.getReader(); + const writer = writable.getWriter(); + + await writer.write("ping"); + writeToScreen("SENT: ping"); + + while (true) { + const { value, done } = await reader.read(); + writeToScreen(`RECEIVED: ${value}`); + if (done) { + break; + } + + setTimeout(async () => { + await writer.write("ping"); + writeToScreen("SENT: ping"); + }, 5000); + } + } + + start(); + + wss.closed.then((result) => { + writeToScreen( + `DISCONNECTED: code ${result.closeCode}, message "${result.reason}"`, + ); + console.error("Socket closed", result.closeCode, result.reason); + }); +} else { + writeToScreen("Your browser does not support WebSocketStream"); +} +``` + +See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for a complete example with full explanation. + +## Specifications + +Not currently a part of any specification. See https://github.com/whatwg/websockets/pull/48 for standardization progress. + +## Browser compatibility + +{{Compat}} + +## See also + +- [WebSocketStream: integrating streams with the WebSocket API](https://developer.chrome.com/docs/capabilities/web-apis/websocketstream), developer.chrome.com (2020) diff --git a/files/en-us/web/api/websocketstream/index.md b/files/en-us/web/api/websocketstream/index.md new file mode 100644 index 000000000000000..132267f258b254d --- /dev/null +++ b/files/en-us/web/api/websocketstream/index.md @@ -0,0 +1,91 @@ +--- +title: WebSocketStream +slug: Web/API/WebSocketStream +page-type: web-api-interface +status: + - non-standard +browser-compat: api.WebSocketStream +--- + +{{APIRef("WebSockets API")}}{{non-standard_header}} + +The **`WebSocketStream`** interface of the {{domxref("WebSockets API", "WebSockets API", "", "nocode")}} is a promise-based API for connecting to a WebSocket server. It uses [streams](/en-US/docs/Web/API/Streams_API) to send and receive data on the connection, and can therefore take advantage of stream [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. + +{{InheritanceDiagram}} + +## Constructor + +- {{domxref("WebSocketStream.WebSocketStream", "WebSocketStream()")}} + - : Creates a new `WebSocketStream` object instance. + +## Instance properties + +- {{domxref("WebSocketStream.url", "url")}} {{ReadOnlyInline}} + - : Returns the URL of the WebSocket server that the `WebSocketStream` instance was created with. +- {{domxref("WebSocketStream.closed", "closed")}} {{ReadOnlyInline}} + - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason sent by the server. +- {{domxref("WebSocketStream.opened", "opened")}} {{ReadOnlyInline}} + - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened. The object contains several useful features, including a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. + +## Instance methods + +- {{domxref("WebSocketStream.close", "close()")}} + - : Closes the WebSocket connection. + +## Examples + +```js +const output = document.querySelector("#output"); + +function writeToScreen(message) { + output.insertAdjacentHTML("beforeend", `

${message}

`); +} + +if ("WebSocketStream" in window) { + const wsURL = "ws://127.0.0.1/"; + const wss = new WebSocketStream(wsURL); + + console.log(wss.url); + + async function start() { + const { readable, writable } = await wss.opened; + writeToScreen("CONNECTED"); + const reader = readable.getReader(); + const writer = writable.getWriter(); + + await writer.write("ping"); + writeToScreen("SENT: ping"); + + while (true) { + const { value, done } = await reader.read(); + writeToScreen(`RECEIVED: ${value}`); + if (done) { + break; + } + + setTimeout(async () => { + await writer.write("ping"); + writeToScreen("SENT: ping"); + }, 5000); + } + } + + start(); +} else { + writeToScreen("Your browser does not support WebSocketStream"); +} +``` + +See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for a complete example with full explanation. + +## Specifications + +Not currently a part of any specification. See https://github.com/whatwg/websockets/pull/48 for standardization progress. + +## Browser compatibility + +{{Compat}} + +## See also + +- [WebSocketStream: integrating streams with the WebSocket API](https://developer.chrome.com/docs/capabilities/web-apis/websocketstream), developer.chrome.com (2020) diff --git a/files/en-us/web/api/websocketstream/opened/index.md b/files/en-us/web/api/websocketstream/opened/index.md new file mode 100644 index 000000000000000..5d93462600907a8 --- /dev/null +++ b/files/en-us/web/api/websocketstream/opened/index.md @@ -0,0 +1,87 @@ +--- +title: "WebSocketStream: opened property" +short-title: opened +slug: Web/API/WebSocketStream/opened +page-type: web-api-instance-property +status: + - non-standard +browser-compat: api.WebSocketStream.opened +--- + +{{APIRef("WebSockets API")}}{{non-standard_header}} + +The **`opened`** read-only property of the +{{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened. The object contains several useful features, including a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. + +## Value + +A promise, which fulfills with an object containing the following properties: + +- `extensions` + - : A string representing any extensions applied to the `WebSocketStream`. Such extensions are not currently defined, but may be in the future. Currently returns an empty string. +- `protocol` + - : A string representing any custom protocol used to open the current WebSocket connection, as specified in the [`protocols`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#protocols) option of the `WebSocketStream()` constructor. Returns an empty string if no custom protocol was used. +- `readable` + - : A {{domxref("ReadableStream")}} instance. Call {{domxref("ReadableStream.getReader()")}} on it to obtain a {{domxref("ReadableStreamDefaultReader")}} instance that can be used to read incoming WebSocket data. +- `writable` + - : A {{domxref("WritableStream")}} instance. Call {{domxref("WritableStream.getWriter()")}} on it to obtain a {{domxref("WritableStreamDefaultWriter")}} instance that can be used to write data to the WebSocket connection. + +The promise rejects in the event of an unclean close. + +## Examples + +```js +const output = document.querySelector("#output"); + +function writeToScreen(message) { + output.insertAdjacentHTML("beforeend", `

${message}

`); +} + +if ("WebSocketStream" in window) { + const wsURL = "ws://127.0.0.1/"; + const wss = new WebSocketStream(wsURL); + + console.log(wss.url); + + async function start() { + const { readable, writable } = await wss.opened; + writeToScreen("CONNECTED"); + const reader = readable.getReader(); + const writer = writable.getWriter(); + + await writer.write("ping"); + writeToScreen("SENT: ping"); + + while (true) { + const { value, done } = await reader.read(); + writeToScreen(`RECEIVED: ${value}`); + if (done) { + break; + } + + setTimeout(async () => { + await writer.write("ping"); + writeToScreen("SENT: ping"); + }, 5000); + } + } + + start(); +} else { + writeToScreen("Your browser does not support WebSocketStream"); +} +``` + +See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for a complete example with full explanation. + +## Specifications + +Not currently a part of any specification. See https://github.com/whatwg/websockets/pull/48 for standardization progress. + +## Browser compatibility + +{{Compat}} + +## See also + +- [WebSocketStream: integrating streams with the WebSocket API](https://developer.chrome.com/docs/capabilities/web-apis/websocketstream), developer.chrome.com (2020) diff --git a/files/en-us/web/api/websocketstream/url/index.md b/files/en-us/web/api/websocketstream/url/index.md new file mode 100644 index 000000000000000..d16076f925cbd4b --- /dev/null +++ b/files/en-us/web/api/websocketstream/url/index.md @@ -0,0 +1,39 @@ +--- +title: "WebSocketStream: url property" +short-title: url +slug: Web/API/WebSocketStream/url +page-type: web-api-instance-property +status: + - non-standard +browser-compat: api.WebSocketStream.url +--- + +{{APIRef("WebSockets API")}}{{non-standard_header}} + +The **`url`** read-only property of the +{{domxref("WebSocketStream")}} interface returns the URL of the WebSocket server that the `WebSocketStream` instance was created with. + +## Value + +A string. + +## Examples + +```js +const wss = new WebSocketStream("example.com/wss"); + +// Logs "example.com/wss" to the console +console.log(wss.url); +``` + +## Specifications + +Not currently a part of any specification. See https://github.com/whatwg/websockets/pull/48 for standardization progress. + +## Browser compatibility + +{{Compat}} + +## See also + +- [WebSocketStream: integrating streams with the WebSocket API](https://developer.chrome.com/docs/capabilities/web-apis/websocketstream), developer.chrome.com (2020) diff --git a/files/en-us/web/api/websocketstream/websocketstream/index.md b/files/en-us/web/api/websocketstream/websocketstream/index.md new file mode 100644 index 000000000000000..2d2f65493d6cc3f --- /dev/null +++ b/files/en-us/web/api/websocketstream/websocketstream/index.md @@ -0,0 +1,77 @@ +--- +title: "WebSocketStream: WebSocketStream() constructor" +short-title: WebSocketStream() +slug: Web/API/WebSocketStream/WebSocketStream +page-type: web-api-constructor +status: + - non-standard +browser-compat: api.WebSocketStream.WebSocketStream +--- + +{{APIRef("WebSockets API")}}{{non-standard_header}} + +The **`WebSocketStream()`** constructor creates a new +{{domxref("WebSocketStream")}} object instance. + +## Syntax + +```js-nolint +new WebSocketStream(url, options) +``` + +### Parameters + +- `url` + - : A string representing the URL of the WebSocket server you want to connect to with this `WebSocketStream` instance. Allowed URL schemes are `"ws"`, `"wss"`, `"http"`, and `"https"`. +- `options` {{optional_inline}} + - : An object than can contain the following properties: + - `protocols` {{optional_inline}} + - : An array of strings representing custom protocols to use for the connection. + - `signal` {{optional_inline}} + - : An {{domxref("AbortSignal")}} belonging to an {{domxref("AbortController")}} that you want to use to close the WebSocket connection. + +### Exceptions + +- `SyntaxError` {{domxref("DOMException")}} + - : Thrown if the URL scheme is not one of `"ws"`, `"wss"`, `"http"`, or `"https"`. + +## Examples + +The most basic example takes the URL of a WebSocket server as an argument: + +```js +const wss = new WebSocketStream("example.com/wss"); +``` + +A more advanced example could also include an options object containing custom protocols and/or an {{domxref("AbortSignal")}}: + +```js +const controller = new AbortController(); +const chatWSS = new WebSocketStream("example.com/chat-wss", { + protocols: ["chat", "chatv2"], + signal: controller.signal, +}); +``` + +At a later time, {{domxref("AbortController.abort()")}} can be called when required to close the connection: + +```js +controller.abort(); +``` + +> [!NOTE] +> Alternatively, you can use the {{domxref("WebSocketStream.close()")}} method to close a connection, however this is mainly needed if you wish to specify a custom code and reason for the server to report. + +See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for a complete example with full explanation. + +## Specifications + +Not currently a part of any specification. See https://github.com/whatwg/websockets/pull/48 for standardization progress. + +## Browser compatibility + +{{Compat}} + +## See also + +- [WebSocketStream: integrating streams with the WebSocket API](https://developer.chrome.com/docs/capabilities/web-apis/websocketstream), developer.chrome.com (2020) From 3c8f35531292071db8ec70b80e87b489245ff961 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:10:57 +0100 Subject: [PATCH 03/16] Update files/en-us/web/api/websockets_api/using_websocketstream/index.md Co-authored-by: Thomas Steiner --- .../en-us/web/api/websockets_api/using_websocketstream/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index 0fac4477b2b1567..e8dd0f314612ca6 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -20,7 +20,7 @@ This article explains how to use the {{domxref("WebSocketStream")}} API to creat To check whether the `WebSocketStream` API is supported, you can use the following: ```js -if ("WebSocketStream" in window) { +if ("WebSocketStream" in self) { // WebSocketStream is supported } ``` From 6ca71c9543126dd8bed9b928b615a48cf9650932 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:11:13 +0100 Subject: [PATCH 04/16] Update files/en-us/web/api/websockets_api/using_websocketstream/index.md Co-authored-by: Thomas Steiner --- .../en-us/web/api/websockets_api/using_websocketstream/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index e8dd0f314612ca6..3e010536a86fc07 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -37,7 +37,7 @@ It can also take an options object containing custom protocols and/or an {{domxr ```js const controller = new AbortController(); -const chatWSS = new WebSocketStream("example.com/chat-wss", { +const chatWSS = new WebSocketStream("wss://example.com/chat", { protocols: ["chat", "chatv2"], signal: controller.signal, }); From 7a55d73b3f1c1a1c485a55a73c33b10f9cc0b162 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:11:26 +0100 Subject: [PATCH 05/16] Update files/en-us/web/api/websockets_api/using_websocketstream/index.md Co-authored-by: Thomas Steiner --- .../en-us/web/api/websockets_api/using_websocketstream/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index 3e010536a86fc07..3b719789f4b5597 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -85,7 +85,7 @@ With `WebSocketStream`, the information previously available via the `WebSocket` const { code, reason } = await wss.closed; ``` -The promise rejects in the event of an unclean close. +The promise rejects in the event of an unclean close operation. As mentioned earlier, the WebSocket connection can be closed using an {{domxref("AbortController")}}. The necessary {{domxref("AbortSignal")}} is passed to the `WebSocketStream` constructor during creation, and {{domxref("AbortController.abort()")}} can then be called when required: From 8f60ee6caa1bdb793729913398f4941b363a8b58 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:11:35 +0100 Subject: [PATCH 06/16] Update files/en-us/web/api/websockets_api/using_websocketstream/index.md Co-authored-by: Thomas Steiner --- .../en-us/web/api/websockets_api/using_websocketstream/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index 3b719789f4b5597..69f037df288c7da 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -91,7 +91,7 @@ As mentioned earlier, the WebSocket connection can be closed using an {{domxref( ```js const controller = new AbortController(); -const wss = new WebSocketStream("example.com/wss", { +const wss = new WebSocketStream("wss://example.com/wss", { signal: controller.signal, }); From a979ad4dfc51df5d4d0f50dc950eae3cc6ca71b8 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:11:50 +0100 Subject: [PATCH 07/16] Update files/en-us/web/api/websockets_api/using_websocketstream/index.md Co-authored-by: Thomas Steiner --- .../en-us/web/api/websockets_api/using_websocketstream/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index 69f037df288c7da..8a3ec05124883dd 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -132,7 +132,7 @@ function writeToScreen(message) { Next, we create an `if ... else` structure to feature detect `WebSocketStream` and output an informative message on non-supporting browsers: ```js -if ("WebSocketStream" in window) { +if ("WebSocketStream" in self) { // supporting code path } else { writeToScreen("Your browser does not support WebSocketStream"); From 1016e4080a25c7a6b4a9d5e540df3b0a7fb9f80f Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:12:00 +0100 Subject: [PATCH 08/16] Update files/en-us/web/api/websockets_api/using_websocketstream/index.md Co-authored-by: Thomas Steiner --- .../en-us/web/api/websockets_api/using_websocketstream/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index 8a3ec05124883dd..67d1c2420f97b8a 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -219,7 +219,7 @@ setTimeout(async () => { output.insertAdjacentHTML("beforeend", `

${message}

`); } - if ("WebSocketStream" in window) { + if ("WebSocketStream" in self) { const wsURL = "ws://127.0.0.1/"; const wss = new WebSocketStream(wsURL); From f2e04e4585651ad659e70a4ed714995269f71cbc Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:12:19 +0100 Subject: [PATCH 09/16] Update files/en-us/web/api/websocketstream/closed/index.md Co-authored-by: Thomas Steiner --- files/en-us/web/api/websocketstream/closed/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websocketstream/closed/index.md b/files/en-us/web/api/websocketstream/closed/index.md index b4d6fcc270dd1fa..799428001ae2209 100644 --- a/files/en-us/web/api/websocketstream/closed/index.md +++ b/files/en-us/web/api/websocketstream/closed/index.md @@ -31,7 +31,7 @@ function writeToScreen(message) { output.insertAdjacentHTML("beforeend", `

${message}

`); } -if ("WebSocketStream" in window) { +if ("WebSocketStream" in self) { const wsURL = "ws://127.0.0.1/"; const wss = new WebSocketStream(wsURL); From 2c01603e81bfa22ad289774e611f9f7d8f7367a7 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:12:26 +0100 Subject: [PATCH 10/16] Update files/en-us/web/api/websocketstream/opened/index.md Co-authored-by: Thomas Steiner --- files/en-us/web/api/websocketstream/opened/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websocketstream/opened/index.md b/files/en-us/web/api/websocketstream/opened/index.md index 5d93462600907a8..0801ea079611ba9 100644 --- a/files/en-us/web/api/websocketstream/opened/index.md +++ b/files/en-us/web/api/websocketstream/opened/index.md @@ -37,7 +37,7 @@ function writeToScreen(message) { output.insertAdjacentHTML("beforeend", `

${message}

`); } -if ("WebSocketStream" in window) { +if ("WebSocketStream" in self) { const wsURL = "ws://127.0.0.1/"; const wss = new WebSocketStream(wsURL); From 76923cc0133dc21730a1531e59ed3537744692a2 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:12:35 +0100 Subject: [PATCH 11/16] Update files/en-us/web/api/websocketstream/url/index.md Co-authored-by: Thomas Steiner --- files/en-us/web/api/websocketstream/url/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websocketstream/url/index.md b/files/en-us/web/api/websocketstream/url/index.md index d16076f925cbd4b..67549addb813c43 100644 --- a/files/en-us/web/api/websocketstream/url/index.md +++ b/files/en-us/web/api/websocketstream/url/index.md @@ -20,7 +20,7 @@ A string. ## Examples ```js -const wss = new WebSocketStream("example.com/wss"); +const wss = new WebSocketStream("wss://example.com/wss"); // Logs "example.com/wss" to the console console.log(wss.url); From 71defc9ad804dd9b930c7743f6e9b007887d6127 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:12:45 +0100 Subject: [PATCH 12/16] Update files/en-us/web/api/websocketstream/websocketstream/index.md Co-authored-by: Thomas Steiner --- files/en-us/web/api/websocketstream/websocketstream/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websocketstream/websocketstream/index.md b/files/en-us/web/api/websocketstream/websocketstream/index.md index 2d2f65493d6cc3f..518399e42c37d0e 100644 --- a/files/en-us/web/api/websocketstream/websocketstream/index.md +++ b/files/en-us/web/api/websocketstream/websocketstream/index.md @@ -40,7 +40,7 @@ new WebSocketStream(url, options) The most basic example takes the URL of a WebSocket server as an argument: ```js -const wss = new WebSocketStream("example.com/wss"); +const wss = new WebSocketStream("wss://example.com/wss"); ``` A more advanced example could also include an options object containing custom protocols and/or an {{domxref("AbortSignal")}}: From 2a82373682640cc1127cd469c9d22551e06ff0db Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 28 Aug 2024 18:34:55 +0100 Subject: [PATCH 13/16] make protocol usage consistent ascross example snippets --- .../web/api/websockets_api/using_websocketstream/index.md | 5 ++++- files/en-us/web/api/websocketstream/close/index.md | 2 +- files/en-us/web/api/websocketstream/closed/index.md | 2 +- files/en-us/web/api/websocketstream/index.md | 4 ++-- files/en-us/web/api/websocketstream/opened/index.md | 2 +- files/en-us/web/api/websocketstream/websocketstream/index.md | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index 67d1c2420f97b8a..ac58313c6a8584d 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -30,7 +30,7 @@ if ("WebSocketStream" in self) { To create a WebSocket client, you first need to create a new `WebSocketStream` instance using the {{domxref("WebSocketStream.WebSocketStream", "WebSocketStream()")}} constructor. In its simplest form, it takes the URL of the WebSocket server as an argument: ```js -const wss = new WebSocketStream("example.com/wss"); +const wss = new WebSocketStream("wss://example.com/wss"); ``` It can also take an options object containing custom protocols and/or an {{domxref("AbortSignal")}} (see [Closing the connection](#closing_the_connection)): @@ -146,6 +146,9 @@ const wsURL = "ws://127.0.0.1/"; const wss = new WebSocketStream(wsURL); ``` +> [!NOTE] +> Best practice is to use secure WebSockets (`wss://`) in production apps. However, in this demo we are connecting to localhost, therefore we need to use the non-secure WebSocket protocol (`ws://`) for the example to work. + The main bulk of our code is contained within the `start()` function, which we define and then immediately invoke. We await the {{domxref("WebSocketStream.opened", "opened")}} promise, then after it fulfills write a message to let the reader know the connection is successful and create {{domxref("ReadableStreamDefaultReader")}} and {{domxref("WritableStreamDefaultWriter")}} instances from the returned `readable` and `writable` properties. Next, we `write()` a `"ping"` value to the socket and communicate that to the user. At this point, the server will respond with a `"pong"` message. We await the `read()` of the read message, communicate it to the user, then write another `"ping"` to the server after a timeout of 5 seconds. This continues the `"ping"`/`"pong"` loop indefinitely. diff --git a/files/en-us/web/api/websocketstream/close/index.md b/files/en-us/web/api/websocketstream/close/index.md index d7a7a114af16bfa..bc488abaeeca92b 100644 --- a/files/en-us/web/api/websocketstream/close/index.md +++ b/files/en-us/web/api/websocketstream/close/index.md @@ -47,7 +47,7 @@ None (`undefined`). ## Examples ```js -const wsURL = "ws://127.0.0.1/"; +const wsURL = "wss://127.0.0.1/"; const wss = new WebSocketStream(wsURL); setTimeout(() => { diff --git a/files/en-us/web/api/websocketstream/closed/index.md b/files/en-us/web/api/websocketstream/closed/index.md index 799428001ae2209..e9196eb5a6669bf 100644 --- a/files/en-us/web/api/websocketstream/closed/index.md +++ b/files/en-us/web/api/websocketstream/closed/index.md @@ -32,7 +32,7 @@ function writeToScreen(message) { } if ("WebSocketStream" in self) { - const wsURL = "ws://127.0.0.1/"; + const wsURL = "wss://127.0.0.1/"; const wss = new WebSocketStream(wsURL); console.log(wss.url); diff --git a/files/en-us/web/api/websocketstream/index.md b/files/en-us/web/api/websocketstream/index.md index 132267f258b254d..2fea9e0141f4755 100644 --- a/files/en-us/web/api/websocketstream/index.md +++ b/files/en-us/web/api/websocketstream/index.md @@ -41,8 +41,8 @@ function writeToScreen(message) { output.insertAdjacentHTML("beforeend", `

${message}

`); } -if ("WebSocketStream" in window) { - const wsURL = "ws://127.0.0.1/"; +if ("WebSocketStream" in self) { + const wsURL = "wss://127.0.0.1/"; const wss = new WebSocketStream(wsURL); console.log(wss.url); diff --git a/files/en-us/web/api/websocketstream/opened/index.md b/files/en-us/web/api/websocketstream/opened/index.md index 0801ea079611ba9..081d6dc68dcc09f 100644 --- a/files/en-us/web/api/websocketstream/opened/index.md +++ b/files/en-us/web/api/websocketstream/opened/index.md @@ -38,7 +38,7 @@ function writeToScreen(message) { } if ("WebSocketStream" in self) { - const wsURL = "ws://127.0.0.1/"; + const wsURL = "wss://127.0.0.1/"; const wss = new WebSocketStream(wsURL); console.log(wss.url); diff --git a/files/en-us/web/api/websocketstream/websocketstream/index.md b/files/en-us/web/api/websocketstream/websocketstream/index.md index 518399e42c37d0e..294889d85e47b1e 100644 --- a/files/en-us/web/api/websocketstream/websocketstream/index.md +++ b/files/en-us/web/api/websocketstream/websocketstream/index.md @@ -47,7 +47,7 @@ A more advanced example could also include an options object containing custom p ```js const controller = new AbortController(); -const chatWSS = new WebSocketStream("example.com/chat-wss", { +const chatWSS = new WebSocketStream("wss://example.com/chat", { protocols: ["chat", "chatv2"], signal: controller.signal, }); From bbb9b374a007a2ac8d370c5a75dc338ea850f665 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Fri, 6 Sep 2024 15:27:57 +0100 Subject: [PATCH 14/16] Fixes for wbamberg review comments --- files/en-us/web/api/websocket/index.md | 3 + files/en-us/web/api/websockets_api/index.md | 11 ++- .../using_websocketstream/index.md | 79 +++++++++++-------- .../web/api/websocketstream/close/index.md | 4 +- .../web/api/websocketstream/closed/index.md | 60 +++----------- files/en-us/web/api/websocketstream/index.md | 5 +- .../web/api/websocketstream/opened/index.md | 54 +++++-------- .../web/api/websocketstream/url/index.md | 1 + .../websocketstream/websocketstream/index.md | 9 ++- 9 files changed, 101 insertions(+), 125 deletions(-) diff --git a/files/en-us/web/api/websocket/index.md b/files/en-us/web/api/websocket/index.md index d0c8e80d7505f93..52c2f03526f8602 100644 --- a/files/en-us/web/api/websocket/index.md +++ b/files/en-us/web/api/websocket/index.md @@ -11,6 +11,9 @@ The `WebSocket` object provides the API for creating and managing a [WebSocket]( To construct a `WebSocket`, use the [`WebSocket()`](/en-US/docs/Web/API/WebSocket/WebSocket) constructor. +> [!NOTE] +> The `WebSocket` API has no way to apply [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure), therefore when messages arrive faster than the application can process them, the application will either fill up the device's memory by buffering those messages, become unresponsive due to 100% CPU usage, or both. For an alterative that provides backpressure automatically, see {{domxref("WebSocketStream")}}. + {{InheritanceDiagram}} ## Constructor diff --git a/files/en-us/web/api/websockets_api/index.md b/files/en-us/web/api/websockets_api/index.md index 894e1946e670cbe..ac2ded90ebf0c36 100644 --- a/files/en-us/web/api/websockets_api/index.md +++ b/files/en-us/web/api/websockets_api/index.md @@ -11,12 +11,15 @@ browser-compat: The **WebSocket API** is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply. -> [!NOTE] -> While a WebSocket connection is functionally somewhat similar to standard Unix-style sockets, they are not related. +The WebSocket API provides two alternative mechanisms for creating and using web socket connections: the {{domxref("WebSocket")}} interface and the {{domxref("WebSocketStream")}} interface. + +- The `WebSocket` interface is stable and has good browser and server support. However it doesn't support [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure). As a result, when messages arrive faster than the application can process them it will either fill up the device's memory by buffering those messages, become unresponsive due to 100% CPU usage, or both. +- The `WebSocketStream` is a {{jsxref("Promise")}}-based alternative to `WebSocket`. It uses the [Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream backpressure automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. However, `WebSocketSteam` is non-standard and currently only supported in one rendering engine. -### WebSocketStream +Additionally, the [WebTransport API](/en-US/docs/Web/API/WebTransport_API) is expected to replace the WebSocket API for many applications. WebTransport is a versatile, low-level API that provides backpressure and many other features not supported by either `WebSocket` or `WebSocketStream`, such as unidirectional streams, out-of-order delivery, and unreliable data transmission via datagrams. WebTransport is more complex to use than WebSockets and its cross-browser support is not as wide, but it enables the implementation of sophisticated solutions. If standard WebSocket connections are a good fit for your use case and you need wide browser compatibility, you should employ the WebSockets API to get up and running quickly. However, if your application requires a non-standard custom solution, then you should use the WebTransport API. -The {{domxref("WebSocketStream")}} API is a modern reimagining of WebSocket client-side JavaScript functionality. It is promise-based, and therefore simpler to work with than the traditional {{domxref("WebSocket")}} API in the modern JavaScript scosystem. In addition, it uses the [Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for more information. +> [!NOTE] +> While a WebSocket connection is functionally somewhat similar to standard Unix-style sockets, they are not related. ## Interfaces diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index ac58313c6a8584d..6886402d2401423 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -3,17 +3,15 @@ title: Using WebSocketStream to write a client slug: Web/API/WebSockets_API/Using_WebSocketStream page-type: guide status: + - experimental - non-standard --- {{DefaultAPISidebar("WebSockets API")}}{{non-standard_header}} -The {{domxref("WebSocketStream")}} API is a modern reimagining of WebSocket client-side JavaScript functionality. It is promise-based, and therefore simpler to work with than the traditional {{domxref("WebSocket")}} API in the modern JavaScript scosystem. In addition, it uses the [Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. +The {{domxref("WebSocketStream")}} API is a {{jsxref("Promise")}}-based alternative to {{domxref("WebSocket")}} for creating and using client-side WebSocket connections. `WebSocketStream` uses the [Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. -This article explains how to use the {{domxref("WebSocketStream")}} API to create a simple WebSocket client. - -> [!NOTE] -> Backpressure is an issue with the traditional `WebSocket` API — it has no way to apply backpressure, therefore when messages arrive faster than the application can process them, the application will either fill up the device's memory by buffering those messages, become unresponsive due to 100% CPU usage, or both. +This article explains how to use the {{domxref("WebSocketStream")}} API to create a WebSocket client. ## Feature detection @@ -61,10 +59,10 @@ const writer = writable.getWriter(); To write data to the socket, you can use {{domxref("WritableStreamDefaultWriter.write()")}}: ```js -await writer.write("My message"); +writer.write("My message"); ``` -To read data from the socket, you can continuously call {{domxref("ReadableStreamDefaultReader.read()")}} until the stream is done (i.e. until the returned `value` is `undefined`, and `done` is `true`): +To read data from the socket, you can continuously call {{domxref("ReadableStreamDefaultReader.read()")}} until the stream has finished, which is indicated by `done` being `true`: ```js while (true) { @@ -85,8 +83,6 @@ With `WebSocketStream`, the information previously available via the `WebSocket` const { code, reason } = await wss.closed; ``` -The promise rejects in the event of an unclean close operation. - As mentioned earlier, the WebSocket connection can be closed using an {{domxref("AbortController")}}. The necessary {{domxref("AbortSignal")}} is passed to the `WebSocketStream` constructor during creation, and {{domxref("AbortController.abort()")}} can then be called when required: ```js @@ -100,7 +96,7 @@ const wss = new WebSocketStream("wss://example.com/wss", { controller.abort(); ``` -Alternatively you can use the {{domxref("WebSocketStream.close()")}} method to close a connection, however this is mainly needed if you wish to specify a custom code and reason for the server to report: +Alternatively you can use the {{domxref("WebSocketStream.close()")}} method to close a connection. This is mainly used if you wish to specify a custom code and reason for the server to report: ```js wss.close({ @@ -119,10 +115,20 @@ To demonstrate basic usage of `WebSocketStream`, we've created a sample client. > [!NOTE] > To get the example working, you'll also need a server component. We wrote our client to work along with the Deno server explained in [Writing a WebSocket server in JavaScript (Deno)](/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno), but any compatible server will do. -First we grab a reference to a DOM element into which we will write output messages, and define a utility function that writes a message to it: +The HTML for the demo is as follows. It includes informational {{htmlelment("h1")}} and {{htmlelment("h1")}} elements, a {{htmlelment("button")}} to close the WebSocket connection that is initially disabled, and a {{htmlelment("div")}} for us to write output messages into. + +```html +

WebSocketStream Test

+

Sends a ping every five seconds

+ +
+``` + +Now on to the JavaScript. First we grab references to the output `
` and the close `
diff --git a/files/en-us/web/api/websocketstream/close/index.md b/files/en-us/web/api/websocketstream/close/index.md index bc488abaeeca92b..6c8b867dd99f327 100644 --- a/files/en-us/web/api/websocketstream/close/index.md +++ b/files/en-us/web/api/websocketstream/close/index.md @@ -4,6 +4,7 @@ short-title: close() slug: Web/API/WebSocketStream/close page-type: web-api-instance-method status: + - experimental - non-standard browser-compat: api.WebSocketStream.close --- @@ -13,8 +14,7 @@ browser-compat: api.WebSocketStream.close The **`close()`** method of the {{domxref("WebSocketStream")}} interface closes the WebSocket connection. The method optionally accepts an object specifying a custom code and reason for the server to report. -> [!NOTE] -> An alternative mechanism for closing a `WebSocketStream` is to specify an {{domxref("AbortSignal")}} in the [`signal`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#signal) option of the constructor upon creation. The associated {{domxref("AbortController")}} can then be used to close the WebSocket connection. This is generally the preferred mechanism, however `close()` can be used if you wish to specify a custom code and reason for the server to report. +An alternative mechanism for closing a `WebSocketStream` is to specify an {{domxref("AbortSignal")}} in the [`signal`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#signal) option of the constructor upon creation. The associated {{domxref("AbortController")}} can then be used to close the WebSocket connection. This is generally the preferred mechanism. However, `close()` can be used if you wish to specify a custom code and reason for the server to report. ## Syntax diff --git a/files/en-us/web/api/websocketstream/closed/index.md b/files/en-us/web/api/websocketstream/closed/index.md index e9196eb5a6669bf..9248803495ced22 100644 --- a/files/en-us/web/api/websocketstream/closed/index.md +++ b/files/en-us/web/api/websocketstream/closed/index.md @@ -4,6 +4,7 @@ short-title: closed slug: Web/API/WebSocketStream/closed page-type: web-api-instance-property status: + - experimental - non-standard browser-compat: api.WebSocketStream.closed --- @@ -11,7 +12,7 @@ browser-compat: api.WebSocketStream.closed {{APIRef("WebSockets API")}}{{non-standard_header}} The **`closed`** read-only property of the -{{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason sent by the server. +{{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason, as sent by the server. ## Value @@ -22,55 +23,20 @@ A promise, which fulfills with an object containing the following properties: - `reason` - : A string representing a human-readable description of the reason why the socket connection is closed. +The promise rejects if the WebSocket connection did not close cleanly (for a clean close, the associated TCP connection must be closed _after_ the WebSocket closing handshake is completed). + ## Examples ```js -const output = document.querySelector("#output"); - -function writeToScreen(message) { - output.insertAdjacentHTML("beforeend", `

${message}

`); -} - -if ("WebSocketStream" in self) { - const wsURL = "wss://127.0.0.1/"; - const wss = new WebSocketStream(wsURL); - - console.log(wss.url); - - async function start() { - const { readable, writable } = await wss.opened; - writeToScreen("CONNECTED"); - const reader = readable.getReader(); - const writer = writable.getWriter(); - - await writer.write("ping"); - writeToScreen("SENT: ping"); - - while (true) { - const { value, done } = await reader.read(); - writeToScreen(`RECEIVED: ${value}`); - if (done) { - break; - } - - setTimeout(async () => { - await writer.write("ping"); - writeToScreen("SENT: ping"); - }, 5000); - } - } - - start(); - - wss.closed.then((result) => { - writeToScreen( - `DISCONNECTED: code ${result.closeCode}, message "${result.reason}"`, - ); - console.error("Socket closed", result.closeCode, result.reason); - }); -} else { - writeToScreen("Your browser does not support WebSocketStream"); -} +const wsURL = "wss://127.0.0.1/"; +const wss = new WebSocketStream(wsURL); + +wss.closed.then((result) => { + writeToScreen( + `DISCONNECTED: code ${result.closeCode}, message "${result.reason}"`, + ); + console.log("Socket closed", result.closeCode, result.reason); +}); ``` See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for a complete example with full explanation. diff --git a/files/en-us/web/api/websocketstream/index.md b/files/en-us/web/api/websocketstream/index.md index 2fea9e0141f4755..53ec4bf74b06fb3 100644 --- a/files/en-us/web/api/websocketstream/index.md +++ b/files/en-us/web/api/websocketstream/index.md @@ -3,6 +3,7 @@ title: WebSocketStream slug: Web/API/WebSocketStream page-type: web-api-interface status: + - experimental - non-standard browser-compat: api.WebSocketStream --- @@ -23,9 +24,9 @@ The **`WebSocketStream`** interface of the {{domxref("WebSockets API", "WebSocke - {{domxref("WebSocketStream.url", "url")}} {{ReadOnlyInline}} - : Returns the URL of the WebSocket server that the `WebSocketStream` instance was created with. - {{domxref("WebSocketStream.closed", "closed")}} {{ReadOnlyInline}} - - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason sent by the server. + - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason as sent by the server. - {{domxref("WebSocketStream.opened", "opened")}} {{ReadOnlyInline}} - - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened. The object contains several useful features, including a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. + - : Returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened. Among other features, this object contains a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. ## Instance methods diff --git a/files/en-us/web/api/websocketstream/opened/index.md b/files/en-us/web/api/websocketstream/opened/index.md index 081d6dc68dcc09f..477b8ec069b7c11 100644 --- a/files/en-us/web/api/websocketstream/opened/index.md +++ b/files/en-us/web/api/websocketstream/opened/index.md @@ -4,6 +4,7 @@ short-title: opened slug: Web/API/WebSocketStream/opened page-type: web-api-instance-property status: + - experimental - non-standard browser-compat: api.WebSocketStream.opened --- @@ -11,7 +12,7 @@ browser-compat: api.WebSocketStream.opened {{APIRef("WebSockets API")}}{{non-standard_header}} The **`opened`** read-only property of the -{{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened. The object contains several useful features, including a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. +{{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is successfully opened. Among other features, this object contains a {{domxref("ReadableStream")}} and a {{domxref("WritableStream")}} instance for receiving and sending data on the connection. ## Value @@ -20,55 +21,38 @@ A promise, which fulfills with an object containing the following properties: - `extensions` - : A string representing any extensions applied to the `WebSocketStream`. Such extensions are not currently defined, but may be in the future. Currently returns an empty string. - `protocol` - - : A string representing any custom protocol used to open the current WebSocket connection, as specified in the [`protocols`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#protocols) option of the `WebSocketStream()` constructor. Returns an empty string if no custom protocol was used. + - : A string representing any custom protocol used to open the current WebSocket connection, as specified in the [`protocols`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#protocols) option of the `WebSocketStream()` constructor. Returns an empty string if no custom protocol was set during instantiation. - `readable` - : A {{domxref("ReadableStream")}} instance. Call {{domxref("ReadableStream.getReader()")}} on it to obtain a {{domxref("ReadableStreamDefaultReader")}} instance that can be used to read incoming WebSocket data. - `writable` - : A {{domxref("WritableStream")}} instance. Call {{domxref("WritableStream.getWriter()")}} on it to obtain a {{domxref("WritableStreamDefaultWriter")}} instance that can be used to write data to the WebSocket connection. -The promise rejects in the event of an unclean close. +The promise rejects if the WebSocket connection fails. ## Examples ```js -const output = document.querySelector("#output"); +const wsURL = "wss://127.0.0.1/"; +const wss = new WebSocketStream(wsURL); -function writeToScreen(message) { - output.insertAdjacentHTML("beforeend", `

${message}

`); -} - -if ("WebSocketStream" in self) { - const wsURL = "wss://127.0.0.1/"; - const wss = new WebSocketStream(wsURL); - - console.log(wss.url); +async function start() { + const { readable, writable, extensions, protocol } = await wss.opened; - async function start() { - const { readable, writable } = await wss.opened; - writeToScreen("CONNECTED"); - const reader = readable.getReader(); - const writer = writable.getWriter(); + const reader = readable.getReader(); + const writer = writable.getWriter(); - await writer.write("ping"); - writeToScreen("SENT: ping"); + await writer.write("ping"); - while (true) { - const { value, done } = await reader.read(); - writeToScreen(`RECEIVED: ${value}`); - if (done) { - break; - } - - setTimeout(async () => { - await writer.write("ping"); - writeToScreen("SENT: ping"); - }, 5000); + while (true) { + const { value, done } = await reader.read(); + if (done) { + break; } - } - start(); -} else { - writeToScreen("Your browser does not support WebSocketStream"); + setTimeout(async () => { + await writer.write("ping"); + }, 5000); + } } ``` diff --git a/files/en-us/web/api/websocketstream/url/index.md b/files/en-us/web/api/websocketstream/url/index.md index 67549addb813c43..3761d3d14627ea9 100644 --- a/files/en-us/web/api/websocketstream/url/index.md +++ b/files/en-us/web/api/websocketstream/url/index.md @@ -4,6 +4,7 @@ short-title: url slug: Web/API/WebSocketStream/url page-type: web-api-instance-property status: + - experimental - non-standard browser-compat: api.WebSocketStream.url --- diff --git a/files/en-us/web/api/websocketstream/websocketstream/index.md b/files/en-us/web/api/websocketstream/websocketstream/index.md index 294889d85e47b1e..f35b8b62d9d2a98 100644 --- a/files/en-us/web/api/websocketstream/websocketstream/index.md +++ b/files/en-us/web/api/websocketstream/websocketstream/index.md @@ -4,6 +4,7 @@ short-title: WebSocketStream() slug: Web/API/WebSocketStream/WebSocketStream page-type: web-api-constructor status: + - experimental - non-standard browser-compat: api.WebSocketStream.WebSocketStream --- @@ -16,6 +17,7 @@ The **`WebSocketStream()`** constructor creates a new ## Syntax ```js-nolint +new WebSocketStream(url) new WebSocketStream(url, options) ``` @@ -24,9 +26,9 @@ new WebSocketStream(url, options) - `url` - : A string representing the URL of the WebSocket server you want to connect to with this `WebSocketStream` instance. Allowed URL schemes are `"ws"`, `"wss"`, `"http"`, and `"https"`. - `options` {{optional_inline}} - - : An object than can contain the following properties: + - : An object that can contain the following properties: - `protocols` {{optional_inline}} - - : An array of strings representing custom protocols to use for the connection. + - : A single string or an array of strings representing the sub-protocol(s) that the client would like to use, for example `"chat"` or `"chatv2"`. Subprotocols may be selected from the [IANA WebSocket Subprotocol Name Registry](https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name) or may be custom names jointly understood by the client and the server. A single server can implement multiple WebSocket sub-protocols, and handle different types of interactions depending on the specified value. If it is omitted, an empty array is used by default. - `signal` {{optional_inline}} - : An {{domxref("AbortSignal")}} belonging to an {{domxref("AbortController")}} that you want to use to close the WebSocket connection. @@ -59,8 +61,7 @@ At a later time, {{domxref("AbortController.abort()")}} can be called when requi controller.abort(); ``` -> [!NOTE] -> Alternatively, you can use the {{domxref("WebSocketStream.close()")}} method to close a connection, however this is mainly needed if you wish to specify a custom code and reason for the server to report. +Alternatively, you can use the {{domxref("WebSocketStream.close()")}} method to close a connection, however this is mainly needed if you wish to specify a custom code and reason for the server to report. See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for a complete example with full explanation. From 2c2ceedd6a26517ec4ed4e694e9e773876878d53 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Mon, 9 Sep 2024 12:46:51 +0100 Subject: [PATCH 15/16] Fixes for 2nd round of wbamberg review --- files/en-us/web/api/closeevent/code/index.md | 4 +- files/en-us/web/api/closeevent/index.md | 2 +- files/en-us/web/api/websockets_api/index.md | 2 +- .../using_websocketstream/index.md | 41 ++++++++++++------- .../web/api/websocketstream/close/index.md | 6 +-- .../web/api/websocketstream/closed/index.md | 6 +-- files/en-us/web/api/websocketstream/index.md | 21 ++++++---- .../web/api/websocketstream/opened/index.md | 8 ++-- .../websocketstream/websocketstream/index.md | 4 +- 9 files changed, 55 insertions(+), 39 deletions(-) diff --git a/files/en-us/web/api/closeevent/code/index.md b/files/en-us/web/api/closeevent/code/index.md index 50f222f3e4c4d89..dd80a8d6f92fcc5 100644 --- a/files/en-us/web/api/closeevent/code/index.md +++ b/files/en-us/web/api/closeevent/code/index.md @@ -8,11 +8,11 @@ browser-compat: api.CloseEvent.code {{APIRef("Websockets API")}} -The **`code`** read-only property of the {{domxref("CloseEvent")}} interface returns a [WebSocket connection close code](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5) indicating the reason the server gave for closing the connection. +The **`code`** read-only property of the {{domxref("CloseEvent")}} interface returns a [WebSocket connection close code](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5) indicating the reason the connection was closed. ## Value -An integer [WebSocket connection close code](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5) in the range `1000` - `4999`, indicating the reason the server gave for closing the connection. +An integer [WebSocket connection close code](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5) in the range `1000` - `4999`, indicating the reason the connection was closed. diff --git a/files/en-us/web/api/closeevent/index.md b/files/en-us/web/api/closeevent/index.md index 237ddd3b628cdbf..2800eb565cadec7 100644 --- a/files/en-us/web/api/closeevent/index.md +++ b/files/en-us/web/api/closeevent/index.md @@ -21,7 +21,7 @@ A `CloseEvent` is sent to clients using {{Glossary("WebSockets")}} when the conn _This interface also inherits properties from its parent, {{domxref("Event")}}._ - {{domxref("CloseEvent.code")}} {{ReadOnlyInline}} - - : Returns an `unsigned short` containing the close code sent by the server. + - : Returns an `unsigned short` containing the close code. - {{domxref("CloseEvent.reason")}} {{ReadOnlyInline}} - : Returns a string indicating the reason the server closed the connection. This is specific to the particular server and sub-protocol. - {{domxref("CloseEvent.wasClean")}} {{ReadOnlyInline}} diff --git a/files/en-us/web/api/websockets_api/index.md b/files/en-us/web/api/websockets_api/index.md index ac2ded90ebf0c36..0afe1654332b5ad 100644 --- a/files/en-us/web/api/websockets_api/index.md +++ b/files/en-us/web/api/websockets_api/index.md @@ -9,7 +9,7 @@ browser-compat: {{DefaultAPISidebar("WebSockets API")}} -The **WebSocket API** is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply. +The **WebSocket API** makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive responses without having to poll the server for a reply. The WebSocket API provides two alternative mechanisms for creating and using web socket connections: the {{domxref("WebSocket")}} interface and the {{domxref("WebSocketStream")}} interface. diff --git a/files/en-us/web/api/websockets_api/using_websocketstream/index.md b/files/en-us/web/api/websockets_api/using_websocketstream/index.md index 6886402d2401423..2ed559edbdbdbda 100644 --- a/files/en-us/web/api/websockets_api/using_websocketstream/index.md +++ b/files/en-us/web/api/websockets_api/using_websocketstream/index.md @@ -9,7 +9,7 @@ status: {{DefaultAPISidebar("WebSockets API")}}{{non-standard_header}} -The {{domxref("WebSocketStream")}} API is a {{jsxref("Promise")}}-based alternative to {{domxref("WebSocket")}} for creating and using client-side WebSocket connections. `WebSocketStream` uses the [Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically, regulating the speed of reading or writing to avoid bottlenecks in the application. +The {{domxref("WebSocketStream")}} API is a {{jsxref("Promise")}}-based alternative to {{domxref("WebSocket")}} for creating and using client-side WebSocket connections. `WebSocketStream` uses the [Streams API](/en-US/docs/Web/API/Streams_API) to handle receiving and sending messages, meaning that socket connections can take advantage of stream [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure) automatically (no additional action required by the developer), regulating the speed of reading or writing to avoid bottlenecks in the application. This article explains how to use the {{domxref("WebSocketStream")}} API to create a WebSocket client. @@ -75,9 +75,11 @@ while (true) { } ``` +The browser automatically controls the rate at which the client receives and sends data by applying backpressure when needed. If data is arriving faster than the client can `read()` it, the underlying Streams API exerts backpressure on the server. In addition, `write()` operations will only proceed if it is safe to do so. + ## Closing the connection -With `WebSocketStream`, the information previously available via the `WebSocket` {{domxref("WebSocket.close_event", "close")}} and {{domxref("WebSocket.error_event", "error")}} events is now available via the {{domxref("WebSocketStream.closed", "closed")}} property — this returns a promise that fulfills with an object containing the closing code (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)) and reason sent by the server: +With `WebSocketStream`, the information previously available via the `WebSocket` {{domxref("WebSocket.close_event", "close")}} and {{domxref("WebSocket.error_event", "error")}} events is now available via the {{domxref("WebSocketStream.closed", "closed")}} property — this returns a promise that fulfills with an object containing the closing code (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)) and reason indicating why the server closed the connection: ```js const { code, reason } = await wss.closed; @@ -96,7 +98,7 @@ const wss = new WebSocketStream("wss://example.com/wss", { controller.abort(); ``` -Alternatively you can use the {{domxref("WebSocketStream.close()")}} method to close a connection. This is mainly used if you wish to specify a custom code and reason for the server to report: +Alternatively you can use the {{domxref("WebSocketStream.close()")}} method to close a connection. This is mainly used if you wish to specify a custom code and/or reason: ```js wss.close({ @@ -115,7 +117,7 @@ To demonstrate basic usage of `WebSocketStream`, we've created a sample client. > [!NOTE] > To get the example working, you'll also need a server component. We wrote our client to work along with the Deno server explained in [Writing a WebSocket server in JavaScript (Deno)](/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno), but any compatible server will do. -The HTML for the demo is as follows. It includes informational {{htmlelment("h1")}} and {{htmlelment("h1")}} elements, a {{htmlelment("button")}} to close the WebSocket connection that is initially disabled, and a {{htmlelment("div")}} for us to write output messages into. +The HTML for the demo is as follows. It includes informational [`

`](/en-US/docs/Web/HTML/Element/Heading_Elements) and {{htmlelement("p")}} elements, a {{htmlelement("button")}} to close the WebSocket connection that is initially disabled, and a {{htmlelement("div")}} for us to write output messages into. ```html

WebSocketStream Test

@@ -131,7 +133,9 @@ const output = document.querySelector("#output"); const closeBtn = document.querySelector("#close"); function writeToScreen(message) { - output.insertAdjacentHTML("beforeend", `

${message}

`); + const pElem = document.createElement("p"); + pElem.textContent = message; + output.appendChild(pElem); } ``` @@ -167,7 +171,7 @@ async function start() { const reader = readable.getReader(); const writer = writable.getWriter(); - await writer.write("ping"); + writer.write("ping"); writeToScreen("SENT: ping"); while (true) { @@ -178,8 +182,12 @@ async function start() { } setTimeout(async () => { - await writer.write("ping"); - writeToScreen("SENT: ping"); + try { + await writer.write("ping"); + writeToScreen("SENT: ping"); + } catch (e) { + writeToScreen(`Error writing to socket: ${e.message}`); + } }, 5000); } } @@ -187,6 +195,9 @@ async function start() { start(); ``` +> [!NOTE] +> The {{domxref("setTimeout")}} function wraps the `write()` call in a [`try...catch`](/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) block to handle any errors that can arise if the application tries to write to the stream after it has been closed. + We now include a promise-style code section to inform the user of the code and reason if the WebSocket connection is closed, as signalled by the {{domxref("WebSocketStream.closed", "closed")}} promise fulfilling: ```js @@ -207,7 +218,7 @@ closeBtn.addEventListener("click", () => { reason: "That's all folks", }); - closeBtn.disabled = false; + closeBtn.disabled = true; }); ``` @@ -231,7 +242,9 @@ closeBtn.addEventListener("click", () => { const closeBtn = document.querySelector("#close"); function writeToScreen(message) { - output.insertAdjacentHTML("beforeend", `

${message}

`); + const pElem = document.createElement("p"); + pElem.textContent = message; + output.appendChild(pElem); } if (!("WebSocketStream" in self)) { @@ -249,7 +262,7 @@ closeBtn.addEventListener("click", () => { const reader = readable.getReader(); const writer = writable.getWriter(); - await writer.write("ping"); + writer.write("ping"); writeToScreen("SENT: ping"); while (true) { @@ -259,8 +272,8 @@ closeBtn.addEventListener("click", () => { break; } - setTimeout(async () => { - await writer.write("ping"); + setTimeout(() => { + writer.write("ping"); writeToScreen("SENT: ping"); }, 5000); } @@ -281,7 +294,7 @@ closeBtn.addEventListener("click", () => { reason: "That's all folks", }); - closeBtn.disabled = false; + closeBtn.disabled = true; }); } diff --git a/files/en-us/web/api/websocketstream/close/index.md b/files/en-us/web/api/websocketstream/close/index.md index 6c8b867dd99f327..e5da648f510653a 100644 --- a/files/en-us/web/api/websocketstream/close/index.md +++ b/files/en-us/web/api/websocketstream/close/index.md @@ -12,9 +12,9 @@ browser-compat: api.WebSocketStream.close {{APIRef("WebSockets API")}}{{non-standard_header}} The **`close()`** method of the -{{domxref("WebSocketStream")}} interface closes the WebSocket connection. The method optionally accepts an object specifying a custom code and reason for the server to report. +{{domxref("WebSocketStream")}} interface closes the WebSocket connection. The method optionally accepts an object containing a custom code and/or reason indicating why the connection was closed. -An alternative mechanism for closing a `WebSocketStream` is to specify an {{domxref("AbortSignal")}} in the [`signal`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#signal) option of the constructor upon creation. The associated {{domxref("AbortController")}} can then be used to close the WebSocket connection. This is generally the preferred mechanism. However, `close()` can be used if you wish to specify a custom code and reason for the server to report. +An alternative mechanism for closing a `WebSocketStream` is to specify an {{domxref("AbortSignal")}} in the [`signal`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#signal) option of the constructor upon creation. The associated {{domxref("AbortController")}} can then be used to close the WebSocket connection. This is generally the preferred mechanism. However, `close()` can be used if you wish to specify a custom code and/or reason. ## Syntax @@ -28,7 +28,7 @@ close(options) - `options` {{optional_inline}} - : An options object containing the following properties: - `closeCode` {{optional_inline}} - - : A number representing the closing code sent by the server (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)). + - : A number representing the closing code (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)). - `reason` {{optional_inline}} - : A string representing a human-readable description of the reason why the socket connection is closed. The maximum allowed length for a `reason` string is 123 bytes. The string is automatically encoded as UTF-8 when the function is invoked. diff --git a/files/en-us/web/api/websocketstream/closed/index.md b/files/en-us/web/api/websocketstream/closed/index.md index 9248803495ced22..333fd2cd9ff3861 100644 --- a/files/en-us/web/api/websocketstream/closed/index.md +++ b/files/en-us/web/api/websocketstream/closed/index.md @@ -12,16 +12,16 @@ browser-compat: api.WebSocketStream.closed {{APIRef("WebSockets API")}}{{non-standard_header}} The **`closed`** read-only property of the -{{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason, as sent by the server. +{{domxref("WebSocketStream")}} interface returns a {{jsxref("Promise")}} that fulfills with an object once the socket connection is closed. The object contains the closing code and reason. ## Value A promise, which fulfills with an object containing the following properties: - `closeCode` - - : A number representing the closing code sent by the server (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)). + - : A number representing the closing code (see the full list of [`CloseEvent` status codes](/en-US/docs/Web/API/CloseEvent/code#value)). - `reason` - - : A string representing a human-readable description of the reason why the socket connection is closed. + - : A string representing a human-readable description of the reason why the socket connection was closed. The promise rejects if the WebSocket connection did not close cleanly (for a clean close, the associated TCP connection must be closed _after_ the WebSocket closing handshake is completed). diff --git a/files/en-us/web/api/websocketstream/index.md b/files/en-us/web/api/websocketstream/index.md index 53ec4bf74b06fb3..462c2f47157b75e 100644 --- a/files/en-us/web/api/websocketstream/index.md +++ b/files/en-us/web/api/websocketstream/index.md @@ -39,22 +39,27 @@ The **`WebSocketStream`** interface of the {{domxref("WebSockets API", "WebSocke const output = document.querySelector("#output"); function writeToScreen(message) { - output.insertAdjacentHTML("beforeend", `

${message}

`); + const pElem = document.createElement("p"); + pElem.textContent = message; + output.appendChild(pElem); } -if ("WebSocketStream" in self) { - const wsURL = "wss://127.0.0.1/"; +if (!("WebSocketStream" in self)) { + writeToScreen("Your browser does not support WebSocketStream"); +} else { + const wsURL = "ws://127.0.0.1/"; const wss = new WebSocketStream(wsURL); console.log(wss.url); async function start() { - const { readable, writable } = await wss.opened; + const { readable, writable, extensions, protocol } = await wss.opened; writeToScreen("CONNECTED"); + closeBtn.disabled = false; const reader = readable.getReader(); const writer = writable.getWriter(); - await writer.write("ping"); + writer.write("ping"); writeToScreen("SENT: ping"); while (true) { @@ -64,16 +69,14 @@ if ("WebSocketStream" in self) { break; } - setTimeout(async () => { - await writer.write("ping"); + setTimeout(() => { + writer.write("ping"); writeToScreen("SENT: ping"); }, 5000); } } start(); -} else { - writeToScreen("Your browser does not support WebSocketStream"); } ``` diff --git a/files/en-us/web/api/websocketstream/opened/index.md b/files/en-us/web/api/websocketstream/opened/index.md index 477b8ec069b7c11..7526f783c11a5d5 100644 --- a/files/en-us/web/api/websocketstream/opened/index.md +++ b/files/en-us/web/api/websocketstream/opened/index.md @@ -21,7 +21,7 @@ A promise, which fulfills with an object containing the following properties: - `extensions` - : A string representing any extensions applied to the `WebSocketStream`. Such extensions are not currently defined, but may be in the future. Currently returns an empty string. - `protocol` - - : A string representing any custom protocol used to open the current WebSocket connection, as specified in the [`protocols`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#protocols) option of the `WebSocketStream()` constructor. Returns an empty string if no custom protocol was set during instantiation. + - : A string representing the sub-protocol used to open the current WebSocket connection (chosen from the options specified in the [`protocols`](/en-US/docs/Web/API/WebSocketStream/WebSocketStream#protocols) option of the `WebSocketStream()` constructor). Returns an empty string if no sub-protocol has been used to open the connection (i.e. no sub-protocol options were included in the constructor call). - `readable` - : A {{domxref("ReadableStream")}} instance. Call {{domxref("ReadableStream.getReader()")}} on it to obtain a {{domxref("ReadableStreamDefaultReader")}} instance that can be used to read incoming WebSocket data. - `writable` @@ -41,7 +41,7 @@ async function start() { const reader = readable.getReader(); const writer = writable.getWriter(); - await writer.write("ping"); + writer.write("ping"); while (true) { const { value, done } = await reader.read(); @@ -49,8 +49,8 @@ async function start() { break; } - setTimeout(async () => { - await writer.write("ping"); + setTimeout(() => { + writer.write("ping"); }, 5000); } } diff --git a/files/en-us/web/api/websocketstream/websocketstream/index.md b/files/en-us/web/api/websocketstream/websocketstream/index.md index f35b8b62d9d2a98..d73e1a642ab8a66 100644 --- a/files/en-us/web/api/websocketstream/websocketstream/index.md +++ b/files/en-us/web/api/websocketstream/websocketstream/index.md @@ -28,7 +28,7 @@ new WebSocketStream(url, options) - `options` {{optional_inline}} - : An object that can contain the following properties: - `protocols` {{optional_inline}} - - : A single string or an array of strings representing the sub-protocol(s) that the client would like to use, for example `"chat"` or `"chatv2"`. Subprotocols may be selected from the [IANA WebSocket Subprotocol Name Registry](https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name) or may be custom names jointly understood by the client and the server. A single server can implement multiple WebSocket sub-protocols, and handle different types of interactions depending on the specified value. If it is omitted, an empty array is used by default. + - : A single string or an array of strings representing the sub-protocol(s) that the client would like to use, for example `"chat"` or `"chatv2"`. Subprotocols may be selected from the [IANA WebSocket Subprotocol Name Registry](https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name) or may be custom names jointly understood by the client and the server. A single server can implement multiple WebSocket sub-protocols, and handle different types of interactions depending on the specified value. If it is omitted, an empty array is used by default. If `protocols` is included, the connection will only be established if the server reports that it has selected one of these sub-protocols. - `signal` {{optional_inline}} - : An {{domxref("AbortSignal")}} belonging to an {{domxref("AbortController")}} that you want to use to close the WebSocket connection. @@ -61,7 +61,7 @@ At a later time, {{domxref("AbortController.abort()")}} can be called when requi controller.abort(); ``` -Alternatively, you can use the {{domxref("WebSocketStream.close()")}} method to close a connection, however this is mainly needed if you wish to specify a custom code and reason for the server to report. +Alternatively, you can use the {{domxref("WebSocketStream.close()")}} method to close a connection, however this is mainly needed if you wish to specify a custom code and/or reason for the server to report. See [Using WebSocketStream to write a client](/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream) for a complete example with full explanation. From 7bc83c3a683807a29c9ebd6eca327a996d6dc839 Mon Sep 17 00:00:00 2001 From: wbamberg Date: Thu, 12 Sep 2024 11:11:10 -0700 Subject: [PATCH 16/16] typo --- files/en-us/web/api/websocket/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/websocket/index.md b/files/en-us/web/api/websocket/index.md index 52c2f03526f8602..48e06a3f1c1a89e 100644 --- a/files/en-us/web/api/websocket/index.md +++ b/files/en-us/web/api/websocket/index.md @@ -12,7 +12,7 @@ The `WebSocket` object provides the API for creating and managing a [WebSocket]( To construct a `WebSocket`, use the [`WebSocket()`](/en-US/docs/Web/API/WebSocket/WebSocket) constructor. > [!NOTE] -> The `WebSocket` API has no way to apply [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure), therefore when messages arrive faster than the application can process them, the application will either fill up the device's memory by buffering those messages, become unresponsive due to 100% CPU usage, or both. For an alterative that provides backpressure automatically, see {{domxref("WebSocketStream")}}. +> The `WebSocket` API has no way to apply [backpressure](/en-US/docs/Web/API/Streams_API/Concepts#backpressure), therefore when messages arrive faster than the application can process them, the application will either fill up the device's memory by buffering those messages, become unresponsive due to 100% CPU usage, or both. For an alternative that provides backpressure automatically, see {{domxref("WebSocketStream")}}. {{InheritanceDiagram}}