WebSocket#

OneJS provides the standard WebSocket API for persistent, bidirectional communication with servers. Unlike fetch which is request-response, WebSocket keeps a connection open for continuous data exchange.

Basic Usage#

const ws = new WebSocket("wss://echo.example.com")

ws.onopen = () => {
    ws.send("Hello server")
    ws.send(JSON.stringify({ type: "subscribe", channel: "scores" }))
}

ws.onmessage = (event) => {
    console.log("Received:", event.data)
}

ws.onclose = (event) => {
    console.log("Closed:", event.code, event.reason)
}

ws.onerror = () => {
    console.error("WebSocket error")
}

Check readyState before sending outside of onopen:

if (ws.readyState === WebSocket.OPEN) {
    ws.send("safe to send")
}

Binary Data#

Send and receive binary data using ArrayBuffer or TypedArray:

ws.onopen = () => {
    const bytes = new Uint8Array([0x01, 0x02, 0x03])
    ws.send(bytes)

    // TypedArray subviews send only the viewed portion
    const large = new Uint8Array([0, 1, 2, 3, 4, 5])
    const slice = new Uint8Array(large.buffer, 2, 3) // bytes [2, 3, 4]
    ws.send(slice) // sends only 3 bytes, not the whole buffer
}

ws.onmessage = (event) => {
    if (event.data instanceof ArrayBuffer) {
        const view = new Uint8Array(event.data)
        console.log("Binary:", view[0], view[1])
    } else {
        console.log("Text:", event.data)
    }
}

Incoming binary frames are always delivered as ArrayBuffer (binaryType defaults to "arraybuffer").

Subprotocols#

Pass one or more subprotocols to negotiate with the server:

const ws = new WebSocket("wss://example.com/graphql", "graphql-ws")

ws.onopen = () => {
    console.log("Negotiated protocol:", ws.protocol) // "graphql-ws"
}

After the connection opens, ws.protocol contains the server-selected subprotocol (or an empty string if none was negotiated).

Connection States#

ConstantValueDescription
CONNECTING0Connection in progress
OPEN1Connected and ready to send
CLOSING2Close initiated
CLOSED3Connection closed
Constants are accessible on both the class (WebSocket.OPEN) and instances (ws.OPEN).

With React#

Store the connection in a ref to send messages, and clean up on unmount:

function Chat({ serverUrl }) {
    const [messages, setMessages] = useState([])
    const wsRef = useRef(null)

    useEffect(() => {
        const ws = new WebSocket(serverUrl)
        wsRef.current = ws

        ws.onmessage = (event) => {
            const msg = JSON.parse(event.data)
            setMessages((prev) => [...prev, msg])
        }

        return () => ws.close()
    }, [serverUrl])

    function send(text) {
        if (wsRef.current?.readyState === WebSocket.OPEN) {
            wsRef.current.send(JSON.stringify({ text }))
        }
    }

    return (
        <View>
            <ScrollView style={{ flexGrow: 1 }}>
                {messages.map((msg, i) => (
                    <Label key={i} text={msg.text} />
                ))}
            </ScrollView>
            <Button text="Send" onClick={() => send("Hello!")} />
        </View>
    )
}

For automatic reconnection, wrap the logic in a hook:

function useWebSocket(url) {
    const [lastMessage, setLastMessage] = useState(null)
    const wsRef = useRef(null)
    const reconnectTimer = useRef(null)

    function connect() {
        const ws = new WebSocket(url)
        wsRef.current = ws

        ws.onmessage = (event) => {
            setLastMessage(event.data)
        }

        ws.onclose = () => {
            reconnectTimer.current = setTimeout(connect, 3000)
        }
    }

    useEffect(() => {
        connect()
        return () => {
            clearTimeout(reconnectTimer.current)
            wsRef.current?.close()
        }
    }, [url])

    function send(data) {
        if (wsRef.current?.readyState === WebSocket.OPEN) {
            wsRef.current.send(data)
        }
    }

    return { lastMessage, send }
}

Notes#

  • Full EventTarget interface: addEventListener, removeEventListener, and dispatchEvent, alongside on* handler properties
  • Event objects include target and currentTarget set to the WebSocket instance
  • Uses ClientWebSocket on native platforms; browser's native WebSocket on WebGL
  • Connections are automatically cleaned up on live reload