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#
| Constant | Value | Description |
|---|---|---|
CONNECTING | 0 | Connection in progress |
OPEN | 1 | Connected and ready to send |
CLOSING | 2 | Close initiated |
CLOSED | 3 | Connection closed |
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
EventTargetinterface:addEventListener,removeEventListener, anddispatchEvent, alongsideon*handler properties - Event objects include
targetandcurrentTargetset to the WebSocket instance - Uses
ClientWebSocketon native platforms; browser's native WebSocket on WebGL - Connections are automatically cleaned up on live reload