{"id":14036706,"url":"https://github.com/ricea/websocketstream-explainer","last_synced_at":"2025-10-30T05:14:06.017Z","repository":{"id":43771970,"uuid":"196348906","full_name":"ricea/websocketstream-explainer","owner":"ricea","description":"Explainer for the WebSocketStream JavaScript API","archived":false,"fork":false,"pushed_at":"2024-02-22T12:24:38.000Z","size":26,"stargazers_count":125,"open_issues_count":10,"forks_count":5,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-02T04:23:43.886Z","etag":null,"topics":["javascript","streams","web-standards","websocket"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ricea.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-07-11T08:06:28.000Z","updated_at":"2025-02-11T15:50:58.000Z","dependencies_parsed_at":"2024-04-16T07:47:52.617Z","dependency_job_id":"543730d2-ad30-488e-bcb3-3bf487acd014","html_url":"https://github.com/ricea/websocketstream-explainer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ricea/websocketstream-explainer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricea%2Fwebsocketstream-explainer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricea%2Fwebsocketstream-explainer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricea%2Fwebsocketstream-explainer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricea%2Fwebsocketstream-explainer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ricea","download_url":"https://codeload.github.com/ricea/websocketstream-explainer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricea%2Fwebsocketstream-explainer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278891757,"owners_count":26063860,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","streams","web-standards","websocket"],"created_at":"2024-08-12T03:02:09.939Z","updated_at":"2025-10-08T05:23:46.534Z","avatar_url":"https://github.com/ricea.png","language":null,"funding_links":[],"categories":["Others"],"sub_categories":[],"readme":"# WebSocketStream Explained\n\n\n## Introduction\n\nThe [WebSocket API](https://html.spec.whatwg.org/multipage/web-sockets.html)\nprovides a JavaScript interface to the\n[RFC6455](https://tools.ietf.org/html/rfc6455) WebSocket protocol. While it has\nserved well, it is awkward from an ergonomics perspective and is missing the\nimportant feature of\n[backpressure](https://streams.spec.whatwg.org/#backpressure). In particular,\n\n* The\n  [onmessage](https://html.spec.whatwg.org/multipage/web-sockets.html#handler-websocket-onmessage)\n  event will keep firing until the page becomes completely unresponsive. The\n  user agent will buffer incoming messages until it runs out of memory and\n  crashes.\n* The only way to determine when the network or remote server can’t keep up\n  with your sent messages is to test the\n  [bufferedAmount](https://html.spec.whatwg.org/multipage/web-sockets.html#dom-websocket-bufferedamount)\n  attribute. To find out when it is safe to start sending messages again, it is\n  necessary to poll bufferedAmount.\n\nWebSocketStream aims to solve these deficiencies with a new API.\n\nHere’s a basic example of usage of the new API:\n\n```javascript\nconst wss = new WebSocketStream(url);\nconst { readable } = await wss.opened;\nconst reader = readable.getReader();\nwhile (true) {\n  const { value, done } = await reader.read();\n  if (done)\n    break;\n  await process(value);\n}\ndone();\n```\n\nThis is the roughly equivalent code with the old API:\n\n```javascript\nconst ws = new WebSocket(url);\nws.onmessage = evt =\u003e process(evt.data);\nws.onclose = evt =\u003e evt.wasClean ? done() : signalErrorSomehow();\n```\n\nThe major difference is that the second example won’t wait for asynchronous\nactivity in `process()` to complete before calling it again; it will keep\nhammering it as long as messages keep arriving.\n\nAlso note that because the old API was designed before Promises were added to\nthe language, error-handling is awkward.\n\nWriting also uses the backpressure facilities of the Streams API:\n\n```javascript\nconst wss = new WebSocketStream(url);\nconst { writable } = await wss.opened;\nconst writer = writable.getWriter();\nfor await (const message of messages) {\n  await writer.write(message);\n}\n```\n\nThe second argument to WebSocketStream is an option bag to allow for future\nextension. One option is “protocols”, which behaves the same as the second\nargument to the WebSocket constructor:\n\n```javascript\nconst wss = new WebSocketStream(url, {protocols: ['chat', 'chatv2']});\nconst { protocol } = await wss.opened;\n```\n\nThe selected protocol is part of the dictionary available via the\n`wss.opened` promise, along with “extensions”. All the information about\nthe live connection is provided by this promise, since it is not relevant if the\nconnection failed.\n\n```javascript\nconst { readable, writable, protocol, extensions } = await wss.opened;\n```\n\nThe information that was available from the onclose and onerror events in the\nold API is now available via the “closed” Promise. This rejects in the event\nof an unclean close, otherwise it resolves to the code and reason sent by the\nserver.\n\n```javascript\nconst { closeCode, reason } = await wss.closed;\n```\n\nAn AbortSignal passed to the constructor makes it simple to abort the handshake.\n\n```javascript\nconst wss = new WebSocketStream(url, { signal: AbortSignal.timeout(1000) });\n```\n\nThe close method can also be used to abort the handshake, but its main purpose\nis to permit specifying the code and reason which is sent to the server.\n\n```javascript\nwss.close({closeCode: 4000, reason: 'Game over'});\n```\n\n\n## Mapping to the protocol\n\nThere is a 1:1 mapping between WebSocket messages and stream chunks.\n\nEach call to `read()` returns one WebSocket message. If a message is split into\nmultiple frames on the wire, it won't be returned by `read()` until the final\nframe (the one with the FIN flag set) arrives.\n\nWhen `read()` is not called, the browser and operating system will still buffer\ndata to some extent, so backpressure will not be detected immediately by the\nserver.\n\nText messages appear in JavaScript as strings. Binary messages appear as\nUint8Array objects.\n\nA clean close will result in `read()` returning an object with `done` set to\ntrue. An unclean close will result in a rejected promise.\n\nEach call to `write()` (or chunk that is piped into the `writable`) will be\nconverted to one message. The browser may split the message into multiple\nframes. BufferSource (ArrayBuffer or TypedArray) objects will be sent as binary\nWebSocket messages. Any other type will be converted to a string and sent as a\ntext message.\n\nThe promise returned by `write()` will resolve when the message has been\nbuffered (either by the browser or operating system). The size of the buffer is\nfinite but unspecified. It is not a signal that the message has been delivered\nto the WebSocket server (the browser does not have this information).\n\nThe promise returned by `write()` will reject if the connection is closed or\nerrored.\n\n\n## Goals\n\n* Provide a WebSocket API that supports backpressure\n* Provide a modern, ergonomic, easy-to-use WebSocket API\n* Allow for future extension\n\n\n## Non-goals\n\n* Support Blob chunks. The old WebSocket API defaults to receiving messages as\n  Blobs; however, creating and reading Blobs is more costly than creating and\n  reading ArrayBuffers. In practice, even though it requires explicitly setting\n  binaryType, 97% of messages are received as ArrayBuffers. On the send side,\n  sending Blobs adds considerable complexity to the implementation because the\n  contents are not available synchronously. Since less than 4% of sent messages\n  are Blobs it is better to avoid this complexity where we can.\n* Changing, replacing or extending the underlying network protocol.\n  [WebTransport](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport)\n  has many advanced features that are not supported by the WebSocket protocol,\n  such as datagram support over UDP. It should be preferred when advanced\n  networking features are required.\n* Allowing user JavaScript to select [WebSocket\n  extensions](https://tools.ietf.org/html/rfc6455#page-48). Since the server\n  already negotiates the extensions to use, adding additional controls to client\n  JavaScript seems redundant. The existing JavaScript API has never supported\n  this, although some non-browser implementations have added options to the\n  constructor for it.\n\n\n## Non-goals in the first version\n\n* Bring-your-own-buffer reading\n* Reading or writing individual messages as streams (for example, to handle\n  messages larger than memory)\n* Exposing WebSocket [pings and\n  pongs](https://tools.ietf.org/html/rfc6455#page-37) to JavaScript.\n\n\n## Use cases\n\n* High-bandwidth WebSocket applications that need to retain interactivity, in\n  particular video and screen-sharing.\n* Similarly, video capture and other applications that generate a lot of data in\n  the browser that needs to be uploaded to the server. With backpressure, the\n  client can stop producing data rather than accumulating data in memory.\n\n\n## End-user benefits\n\nApplications written with the new API will automatically be more responsive due\nto respecting backpressure. High throughput applications will adapt to the\ncapabilities of the client, providing everyone with a smooth experience.\n\n\n## Alternatives\n\nIt’s possible to implement backpressure at the application level, but it’s\ncomplex and difficult to achieve peak throughput. For example, client JavaScript\ncould send an application-level confirmation message to the server every time it\nfinishes processing a message. The server could keep track of how many messages\nit has sent that have not yet been confirmed, and stop sending if the number\ngets above a certain threshhold.\n\nAside from backpressure, the rest of the API can be emulated by wrapping the\nexisting WebSocket API, but this will not permit future extensions.\n\nAdding new attributes to the existing WebSocket API was considered but not\nadopted because having two APIs on one object would be confusing and create odd\nsemantics.\n\n[WebTransport](https://github.com/WICG/web-transport/blob/master/explainer.md)\nalso provides backpressure, and may replace WebSocket for many purposes. In the\nnear future the WebSocket protocol has the advantage that it works on networks\nthat block QUIC, and has much existing deployed infrastructure.\n\nAn older version of this explainer had the readable stream producing ArrayBuffer\nchunks. This was changed to Uint8Array chunks to align better with WebTransport\nand modern practice.\n\nPreviously the `closeCode` attribute was called `code`, but this conflicted with\nthe `code` attribute of `DOMException`.\n\n\n## Future work\n\n* Adding bring-your-own-buffer reading is a natural extension with the potential\n  to improve performance.\n* Customisable buffer sizes to allow developers to make the trade-off between\n  throughput and response to backpressure explicitly.\n\n\n## See also\n\n* [WebSocketStream design\n    notes](https://docs.google.com/document/d/1La1ehXw76HP6n1uUeks-WJGFgAnpX2tCjKts7QFJ57Y/edit)\n    for definition of the new API and technical details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fricea%2Fwebsocketstream-explainer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fricea%2Fwebsocketstream-explainer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fricea%2Fwebsocketstream-explainer/lists"}