LEARNING OBJECTIVES ⌵
- Initialize WebSocket instances using standard URL schemes (
wss://andws://). - Negotiate application subprotocols via the
protocolsargument and inspectws.protocol. - Bind robust event listeners to the four core lifecycle events:
open,message,error, andclose. - Interpret RFC 6455
CloseEventstatus codes (1000,1001,1006,1011) and distinguish clean disconnects from network failures.
📖 The Mental Model & Story (Intuitive Foundation)
Think of the native WebSocket JavaScript API as an embassy communication station operating a dedicated diplomatic hotline.
Before any confidential intelligence can be passed, four formal operational stages take place:
- The Handshake / Station Activation (
open): The operator plugs in the line and confirms the remote embassy is online and authenticated. - Dispatch Transmissions (
message): Diplomatic pouches (text envelopes or binary containers) arrive across the wire. - Line Anomalies (
error): Electrical interference or protocol breaches trigger an alarm. Due to security protocols, the alarm indicates a disturbance without revealing classified network topology. - Decommissioning (
close): The hotline closes with a formal exit document specifying whether the departure was orderly (code1000) or an emergency wire severance (code1006).
+-----------------------------------------------------------------------------------+
| WEBSOCKET LIFECYCLE EVENTS |
+-----------------------------------------------------------------------------------+
new WebSocket('wss://...')
|
v
[ CONNECTING (0) ] ----(TCP / TLS / HTTP Handshake)
|
+--------------------------------+
| |
v (Success) v (Handshake Failure)
+------------+ +------------+
| 'open' | | 'error' |
+------------+ +------------+
| |
v v
[ OPEN (1) ] <--- 'message' ---> +------------+
| | 'close' |
v (socket.close()) +------------+
[ CLOSING (2) ] |
| v
+-----------------------> [ CLOSED (3) ]
Technical Deep Dive & Specifications
The WebSocket Constructor
The standard browser constructor is defined in the WHATWG HTML / W3C WebSocket API specification:
const socket = new WebSocket(url: string, protocols?: string | string[]);
Parameters:
url(string, required): The target WebSocket endpoint. Must use either thews://(insecure, port 80) orwss://(TLS encrypted, port 443) protocol scheme. Relative URLs are supported in modern browsers (e.g.,new WebSocket('/api/feed')resolves towss://current-origin/api/feed).protocols(string | string[], optional): A subprotocol name or array of subprotocol strings (e.g.,['graphql-transport-ws', 'wamp.2.json']).
Subprotocol Negotiation (Sec-WebSocket-Protocol)
When multiple clients connect to a server, they may support different application protocols (e.g., GraphQL subscriptions, STOMP, or JSON-RPC 2.0).
Browser (Client) Server
| |
| GET /ws HTTP/1.1 |
| Sec-WebSocket-Protocol: graphql-ws, wamp.2.json |
|---------------------------------------------------->|
| |
| HTTP/1.1 101 Switching Protocols |
| Sec-WebSocket-Protocol: graphql-ws |
|<----------------------------------------------------|
| |
socket.protocol === "graphql-ws"
If the server accepts one of the requested subprotocols, it includes the selected string in its handshake response. After the socket opens, the active protocol is exposed on the read-only property socket.protocol.
Instance Properties Matrix
| Property | Type | Description |
|---|---|---|
url |
string |
The absolute URL resolved by the constructor. |
protocol |
string |
The subprotocol selected by the server during the handshake (empty string if none was selected). |
readyState |
number |
The current connection status: 0 (CONNECTING), 1 (OPEN), 2 (CLOSING), 3 (CLOSED). |
bufferedAmount |
number |
Number of bytes of data queued using send() that have not yet been transmitted to the network. |
binaryType |
string |
Controls how incoming binary messages are exposed: 'blob' (default) or 'arraybuffer'. |
extensions |
string |
Active protocol extensions negotiated with the server (e.g., 'permessage-deflate'). |
The Four Core Lifecycle Events
const socket = new WebSocket('wss://echo.websocket.events');
// 1. Connection established
socket.addEventListener('open', (event) => {
console.log('Socket connection established:', event);
socket.send(JSON.stringify({ type: 'GREETING', payload: 'Hello Server!' }));
});
// 2. Incoming message received
socket.addEventListener('message', (event) => {
console.log('Received payload from server:', event.data);
});
// 3. Connection error
socket.addEventListener('error', (event) => {
console.error('WebSocket encountered an error:', event);
});
// 4. Connection closed
socket.addEventListener('close', (event) => {
console.log(`Socket closed with Code: ${event.code}, Reason: "${event.reason}", Clean: ${event.wasClean}`);
});
Event Object Details:
open(Event): Fires whenreadyStatetransitions from0 (CONNECTING)to1 (OPEN). It indicates the handshake succeeded and messages can safely be dispatched.message(MessageEvent):event.data: Contains the message payload (string,Blob, orArrayBuffer).event.origin: The origin of the server (wss://example.com).
error(Event): Triggered when a transport error occurs (e.g., DNS resolution failure, TLS certificate error, or abnormal TCP drop).Security Note: The browser's
errorevent deliberately omits specific network error details to prevent cross-origin port-scanning and network reconnaissance attacks.close(CloseEvent):event.code: The RFC 6455 16-bit status code.event.reason: A human-readable UTF-8 string explanation (up to 123 bytes) supplied by either client or server.event.wasClean: A boolean indicating whether the TCP connection closed via a proper RFC 6455 close handshake (true) or was dropped abruptly (false).
Standard RFC 6455 Close Status Codes
| Code | Name | Initiator | Meaning / Typical Scenario |
|---|---|---|---|
| 1000 | Normal Closure | Either | Purpose accomplished (e.g., user logged out or session ended cleanly). |
| 1001 | Going Away | Either | Endpoint is shutting down (e.g., server restart or browser navigating to another URL). |
| 1002 | Protocol Error | Either | Endpoint received a frame violating RFC 6455 specifications. |
| 1003 | Unsupported Data | Either | Received data type it cannot accept (e.g., text-only server receives binary). |
| 1005 | No Status Received | System | Expected status code but none was provided (reserved value, not sent over wire). |
| 1006 | Abnormal Closure | System | Connection dropped without a close frame (e.g., pulled cable, crash, TLS failure). Never sent in a close frame directly; generated locally by the browser. |
| 1007 | Invalid Frame Payload | Either | Payload data inconsistent with frame type (e.g., non-UTF-8 bytes in text frame). |
| 1008 | Policy Violation | Either | Endpoint received a message violating generic policy (e.g., auth expired). |
| 1009 | Message Too Big | Either | Message size exceeds server buffer limits. |
| 1011 | Internal Server Error | Server | Server terminated connection due to unexpected condition/crash. |
| 4000–4999 | Application Codes | Custom | Reserved for custom private application business logic (e.g., 4001: Session Expired). |
💻 Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Lines 84–97 (
connectBtn.addEventListener): Instantiatesnew WebSocket(url)within atry/catchblock to handle malformed URL strings. - Lines 89–92 (
open): Binds an event listener toopen, updates the UI state badge toOPEN (1), and logs the negotiated subprotocol viasocket.protocol. - Lines 94–96 (
message): Listens to incoming text messages and displaysevent.data. - Lines 98–101 (
error): Captures connection failures. - Lines 103–106 (
close): Inspectsevent.code,event.reason, andevent.wasCleanwhen the TCP socket terminates. - Lines 111–116 (
disconnectBtn.addEventListener): Callssocket.close(1000, "Client initiated closure")to initiate a clean RFC 6455 close handshake.
Expected Browser Render Output
WebSocket Lifecycle Monitor [OPEN (1)]
[ wss://echo.websocket.events ] [Connect] [Disconnect]
[ Hello, WebSocket Server! ] [Send Message]
Event Stream Log
[14:20:01.120] [INFO] Attempting connection to wss://echo.websocket.events...
[14:20:01.340] [OPEN] Connected successfully! Protocol: "none"
[14:20:05.812] [INFO] Dispatched frame: "Hello, WebSocket Server!"
[14:20:05.990] [MESSAGE] Data received: Hello, WebSocket Server!
[14:20:10.100] [INFO] Executing manual socket.close(1000, "Client initiated closure")...
[14:20:10.220] [CLOSE] Connection closed. Code: 1000, Reason: "Client initiated closure", WasClean: true🏋️ Hands-On Exercise
🎯 The Challenge: Build a Resilient Close Code Humanizer & Diagnostic Classifier
Instructions:
- Create a function
classifyCloseEvent(event)that accepts a standardCloseEventobject. - The function must return an object with:
category:'CLEAN_USER','SERVER_INTENTIONAL','NETWORK_FAILURE', or'PROTOCOL_VIOLATION'.description: A clear, professional explanation of why the connection terminated.shouldRetry:truefor unexpected transient drops (1006,1011), andfalsefor user-initiated closures (1000) or fatal violations (1008).
- Bind this classifier to a test simulation UI with a dropdown of status codes.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Calling
socket.send()BeforeopenFires: Instantiatingnew WebSocket()does not synchronously open the connection. Invokingsocket.send()whilereadyState === WebSocket.CONNECTING (0)throws an uncaughtDOMException: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.Always wait for theopenevent. - Overwriting
onmessageHandlers: Assigningws.onmessage = fnoverwrites any previously registered message listener. In modular applications with multiple components, always usews.addEventListener('message', fn). - Expecting Detailed Errors in
onerror: TheErrorEventin WebSocket APIs intentionally contains no stack trace, status code, or network diagnostic payload. To diagnose connection drops, inspect the subsequentcloseevent'scodeandreason.
💡 Pro Tips
- Enforce Subprotocol Versioning: Always pass an array of supported subprotocols (e.g.,
['v2.myapp.com', 'v1.myapp.com']). This enables rolling server upgrades where newer clients negotiatev2while older clients continue onv1seamlessly. - Always Provide Clean Close Reasons: When disconnecting on the client side, call
ws.close(1000, "USER_LOGOUT")orws.close(1000, "TAB_UNMOUNTED"). This provides vital telemetry in your server-side observability logs.
📌 Key Takeaways
- The browser
WebSocketconstructor accepts a target URL and optional subprotocol strings. - The four core lifecycle events are
open,message,error, andclose. socket.protocolexposes the server's negotiated subprotocol.CloseEvent.code === 1006indicates an abnormal network drop without an RFC 6455 close handshake.socket.send()must only be called whensocket.readyState === WebSocket.OPEN (1).- --