Chapter 52: WebSockets in HTML5

The WebSocket JavaScript API

Instantiating connections with 'wss://', managing subprotocols, and handling the core lifecycle events: onopen, onmessage, onerror, and onclose.

LEARNING OBJECTIVES
  • Initialize WebSocket instances using standard URL schemes (wss:// and ws://).
  • Negotiate application subprotocols via the protocols argument and inspect ws.protocol.
  • Bind robust event listeners to the four core lifecycle events: open, message, error, and close.
  • Interpret RFC 6455 CloseEvent status codes (1000, 1001, 1006, 1011) and distinguish clean disconnects from network failures.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 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:

  1. The Handshake / Station Activation (open): The operator plugs in the line and confirms the remote embassy is online and authenticated.
  2. Dispatch Transmissions (message): Diplomatic pouches (text envelopes or binary containers) arrive across the wire.
  3. 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.
  4. Decommissioning (close): The hotline closes with a formal exit document specifying whether the departure was orderly (code 1000) or an emergency wire severance (code 1006).
 +-----------------------------------------------------------------------------------+
 |                             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:

  1. url (string, required): The target WebSocket endpoint. Must use either the ws:// (insecure, port 80) or wss:// (TLS encrypted, port 443) protocol scheme. Relative URLs are supported in modern browsers (e.g., new WebSocket('/api/feed') resolves to wss://current-origin/api/feed).
  2. 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 when readyState transitions from 0 (CONNECTING) to 1 (OPEN). It indicates the handshake succeeded and messages can safely be dispatched.
  • message (MessageEvent):
    • event.data: Contains the message payload (string, Blob, or ArrayBuffer).
    • 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 error event 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): Instantiates new WebSocket(url) within a try/catch block to handle malformed URL strings.
  • Lines 89–92 (open): Binds an event listener to open, updates the UI state badge to OPEN (1), and logs the negotiated subprotocol via socket.protocol.
  • Lines 94–96 (message): Listens to incoming text messages and displays event.data.
  • Lines 98–101 (error): Captures connection failures.
  • Lines 103–106 (close): Inspects event.code, event.reason, and event.wasClean when the TCP socket terminates.
  • Lines 111–116 (disconnectBtn.addEventListener): Calls socket.close(1000, "Client initiated closure") to initiate a clean RFC 6455 close handshake.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
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:

  1. Create a function classifyCloseEvent(event) that accepts a standard CloseEvent object.
  2. 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: true for unexpected transient drops (1006, 1011), and false for user-initiated closures (1000) or fatal violations (1008).
  3. Bind this classifier to a test simulation UI with a dropdown of status codes.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Calling socket.send() Before open Fires: Instantiating new WebSocket() does not synchronously open the connection. Invoking socket.send() while readyState === WebSocket.CONNECTING (0) throws an uncaught DOMException: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state. Always wait for the open event.
  2. Overwriting onmessage Handlers: Assigning ws.onmessage = fn overwrites any previously registered message listener. In modular applications with multiple components, always use ws.addEventListener('message', fn).
  3. Expecting Detailed Errors in onerror: The ErrorEvent in WebSocket APIs intentionally contains no stack trace, status code, or network diagnostic payload. To diagnose connection drops, inspect the subsequent close event's code and reason.

💡 Pro Tips

  1. 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 negotiate v2 while older clients continue on v1 seamlessly.
  2. Always Provide Clean Close Reasons: When disconnecting on the client side, call ws.close(1000, "USER_LOGOUT") or ws.close(1000, "TAB_UNMOUNTED"). This provides vital telemetry in your server-side observability logs.

📌 Key Takeaways

  • The browser WebSocket constructor accepts a target URL and optional subprotocol strings.
  • The four core lifecycle events are open, message, error, and close.
  • socket.protocol exposes the server's negotiated subprotocol.
  • CloseEvent.code === 1006 indicates an abnormal network drop without an RFC 6455 close handshake.
  • socket.send() must only be called when socket.readyState === WebSocket.OPEN (1).
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

What happens if a developer calls socket.send('data') immediately after const socket = new WebSocket('wss://example.com') without waiting?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

Which RFC 6455 status code is generated locally by the browser when a connection drops unexpectedly due to network failure (such as an unplugged cable)?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

Why does the WebSocket error event object not contain detailed diagnostic strings like "404 Not Found" or "TLS Certificate Invalid"?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP