Lifecycle as a state machine
A WebSocket connection moves through four states: CONNECTING (handshake in flight) → OPEN (frames flowing) → CLOSING (one side sent close, the other is acknowledging) → CLOSED (terminal). The browser exposes these as WebSocket.readyState values 0/1/2/3.
The frame
WebSocket data is not a stream of bytes — it is a sequence of frames. Each frame has a small header (2-14 bytes) carrying: a FIN bit (last frame of a message?), an opcode (text, binary, ping, pong, close, or continuation), a mask bit, and the payload length. Client-to-server frames are always masked with a per-frame XOR key; server-to-client frames are not. This masking is anti-cache-poisoning, not security — do not confuse it with encryption.
Close codes
Every clean close carries a code. 1000 is a normal close. 1001 is "going away" (server shutdown, page navigation). 1006 is the special abnormal-close code reserved for "the connection died and no close frame was received." 4000-4999 is reserved for application-defined codes — use them for "session expired", "kicked from room", and so on.