websocket

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: MIT Imports: 18 Imported by: 0

README

host/websocket

A browser-compatible WebSocket client for the gojs JavaScript engine, implemented as an optional host package. It adds a standard WebSocket global to a VM and speaks RFC 6455 directly — no third-party WebSocket library, standard library only.

import "github.com/iceisfun/gojs/host/websocket"

vm := gojs.New(
    gojs.WithPrintProvider(gojs.NewDefaultPrintProvider()),
    gojs.WithTimerProvider(gojs.NewDefaultTimerProvider()),
)
defer vm.Close()

if err := websocket.Install(vm); err != nil {
    log.Fatal(err)
}

vm.RunString("app.js", `
    const ws = new WebSocket("ws://localhost:8080/chat", ["chat"]);
    ws.onopen    = () => ws.send("hello");
    ws.onmessage = (e) => console.log("recv", e.data);
    ws.onclose   = (e) => console.log("closed", e.code, e.reason, e.wasClean);
`)

See examples/websocket for a complete, offline, runnable client + echo server.

Scripting API

Install defines globalThis.WebSocket, matching the WHATWG interface:

  • Constructor: new WebSocket(url[, protocols])url is ws:// or wss://; protocols is a string or array of subprotocol names.
  • Constants (static and instance): CONNECTING=0, OPEN=1, CLOSING=2, CLOSED=3.
  • Properties: url, protocol, extensions, readyState, bufferedAmount, binaryType.
  • Handlers: onopen, onmessage, onerror, onclose.
  • Events: addEventListener / removeEventListener / dispatchEvent for the open, message, error, and close types. Event objects carry type/target; message events add data; close events add code, reason, wasClean; the error event adds message.
  • Methods: send(data) (string → text frame; ArrayBuffer/DataView → binary frame) and close([code[, reason]]).

Install options

websocket.Install(vm,
    websocket.WithMaxFrameSize(1<<20),        // reject a larger single frame → close 1009
    websocket.WithMaxMessageSize(4<<20),      // reject a larger reassembled message → close 1009
    websocket.WithHandshakeTimeout(30*time.Second),
    websocket.WithCloseTimeout(5*time.Second),// wait for the peer's Close echo
    websocket.WithTLSConfig(&tls.Config{...}),// for wss://
    websocket.WithOrigin("https://example.com"),
)
Option Default Effect
WithMaxFrameSize 16 MiB Max payload of one incoming frame (0 = unlimited). Oversized → fail with close 1009.
WithMaxMessageSize 16 MiB Max size of a reassembled message. Oversized → fail with close 1009.
WithHandshakeTimeout 30s Bound on TCP/TLS connect + opening handshake.
WithCloseTimeout 5s How long a client-initiated close waits for the peer's Close echo.
WithTLSConfig derived TLS config for wss:// (ServerName defaults to the URL host).
WithOrigin none Origin header on the opening handshake.

Protocol coverage

  • Opening handshake over ws:// / wss://: random Sec-WebSocket-Key, Sec-WebSocket-Version: 13, optional Sec-WebSocket-Protocol, validation of the 101 response and Sec-WebSocket-Accept. A server that selects an extension we did not offer fails the handshake.
  • Frame codec: FIN/opcode, mandatory client masking with a random 32-bit key, 7/16/64-bit lengths, continuation/fragmentation reassembly.
  • Control frames: ping → automatic pong echoing the payload, pong (ignored), and the close handshake (code + reason, echoed).
  • Failure handling ("Fail the WebSocket Connection"): masked server frame, reserved bits, or bad opcode → 1002; invalid UTF-8 in a text frame → 1007; oversized frame/message → 1009. Each surfaces an error event followed by a close event with wasClean === false. An abrupt transport drop yields close code 1006, not clean.

Server helper

The package also exports a minimal server side used by the example and the tests — Upgrade(w, r, UpgradeConfig) returns a *ServerConn with ReadMessage/WriteMessage/WriteFrame/Ping/Close/CloseNow. It reuses the same frame codec.

Differences from browsers

  • No Blob. Binary messages are always delivered as an ArrayBuffer, and binaryType defaults to "arraybuffer" (browsers default to "blob"). Setting binaryType = "blob" has no effect.
  • No Uint8Array. gojs does not implement the TypedArray family yet, so read incoming bytes with a DataView, and send binary data as an ArrayBuffer (or DataView).
  • No DOMException. send before OPEN throws an Error named InvalidStateError; invalid close arguments throw an Error named InvalidAccessError/SyntaxError.

Threading model

The VM is single-threaded. Each socket runs a reader and a writer goroutine that never touch JavaScript values; every open/message/error/close event is delivered onto the VM goroutine via Enqueue. send/close run on the VM goroutine, copy their payload there, and hand the copy to the writer over a channel. An open socket pins the event loop (so RunString/RunLoop stays alive); closing it releases the pin so the loop can drain and return.

Implementation notes

This package relies on a small binary bridge added to the gojs embedding API: VM.NewArrayBuffer, VM.ArrayBufferBytes, VM.TypedArrayBytes, and VM.Pin (the event-loop keepalive). VM.NewUint8Array exists for API completeness but returns an ArrayBuffer because gojs has no Uint8Array.

Documentation

Overview

Package websocket provides a browser-compatible WebSocket client for the gojs JavaScript engine. Calling Install adds a global WebSocket class to a VM, implemented against a hand-written RFC 6455 client that runs the socket on its own goroutines and delivers open/message/error/close events back onto the VM event loop.

The package depends only on the Go standard library and the gojs embedding API; it pulls in no third-party WebSocket library and implements the framing protocol directly.

Scripting surface

After Install, scripts use the familiar Web API:

const ws = new WebSocket("ws://localhost:8080/chat");
ws.binaryType = "arraybuffer";
ws.onopen    = () => ws.send("hello");
ws.onmessage = (e) => console.log("recv", e.data);
ws.onclose   = (e) => console.log("closed", e.code, e.reason, e.wasClean);
ws.addEventListener("error", (e) => console.log("error", e.message));

Browser differences

gojs has no Blob type, so binary messages are always delivered as an ArrayBuffer regardless of binaryType, and binaryType defaults to "arraybuffer" (the DOM default is "blob"). Everything else — readyState constants, the handler properties, addEventListener/removeEventListener/dispatchEvent for the open/message/error/close types, and the close handshake with code/reason/ wasClean — matches the browser API.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Install

func Install(vm *gojs.VM, opts ...Option) error

Install adds the WebSocket global to vm and wires up the native bridge. It is safe to call once per VM, before or after other RunString calls; the global persists across runs. Options tune frame/message limits, timeouts, TLS, and the handshake Origin.

Types

type MessageType

type MessageType int

MessageType identifies the kind of a WebSocket data message.

const (
	// TextMessage is a UTF-8 text message (opcode 0x1).
	TextMessage MessageType = 1
	// BinaryMessage is a binary message (opcode 0x2).
	BinaryMessage MessageType = 2
)

type Option

type Option func(*options)

Option configures the WebSocket installation.

func WithCloseTimeout

func WithCloseTimeout(d time.Duration) Option

WithCloseTimeout bounds how long a client-initiated close waits for the peer to echo the Close frame before dropping the socket. The default is 5 seconds.

func WithHandshakeTimeout

func WithHandshakeTimeout(d time.Duration) Option

WithHandshakeTimeout bounds the time allowed to establish the TCP/TLS connection and complete the opening handshake. The default is 30 seconds.

func WithMaxFrameSize

func WithMaxFrameSize(n int) Option

WithMaxFrameSize bounds the payload of a single incoming frame. A frame larger than the limit fails the connection with close code 1009. Zero means unlimited. The default is 16 MiB.

func WithMaxMessageSize

func WithMaxMessageSize(n int) Option

WithMaxMessageSize bounds the total size of a reassembled (possibly fragmented) message. Exceeding it fails the connection with close code 1009. The default is 16 MiB.

func WithOrigin

func WithOrigin(origin string) Option

WithOrigin sets an Origin header sent with the opening handshake.

func WithTLSConfig

func WithTLSConfig(c *tls.Config) Option

WithTLSConfig sets the TLS configuration used for wss:// connections. When nil, a default configuration is used with ServerName derived from the URL.

type ServerConn

type ServerConn struct {
	Subproto string
	// contains filtered or unexported fields
}

ServerConn is an accepted server-side WebSocket connection. Its methods are not safe for concurrent use by multiple goroutines except that Close may be called concurrently with Read/Write to unblock them.

func Upgrade

Upgrade performs the server side of the RFC 6455 opening handshake on an incoming HTTP request, hijacks the connection, and returns a ServerConn. The caller owns the connection and must Close it.

func (*ServerConn) Close

func (s *ServerConn) Close(code int, reason string) error

Close sends a Close frame with the given code and reason and then closes the transport.

func (*ServerConn) CloseNow

func (s *ServerConn) CloseNow() error

CloseNow closes the transport without a Close frame, simulating an abrupt disconnect.

func (*ServerConn) Ping

func (s *ServerConn) Ping(payload []byte) error

Ping sends a Ping frame with the given payload.

func (*ServerConn) ReadMessage

func (s *ServerConn) ReadMessage() (MessageType, []byte, error)

ReadMessage reads the next complete data message, transparently answering Ping frames with a Pong and reassembling fragments. It returns (0, nil, err) when the peer sends a Close frame or the connection ends; the error is io.EOF-like or a *wsError describing a protocol violation.

func (*ServerConn) WriteFrame

func (s *ServerConn) WriteFrame(fin bool, opcode byte, payload []byte) error

WriteFrame writes a single raw frame (unmasked, as a server must). It exposes FIN/opcode so callers — including tests — can send fragments or malformed frames deliberately.

func (*ServerConn) WriteMessage

func (s *ServerConn) WriteMessage(typ MessageType, data []byte) error

WriteMessage sends a complete unfragmented data message.

type UpgradeConfig

type UpgradeConfig struct {
	// Subprotocols lists protocols the server supports, in preference order. The
	// first client-offered protocol that appears here is selected.
	Subprotocols []string
	// MaxFrameSize and MaxMessageSize bound incoming frames/messages (0 =
	// unlimited / 16 MiB default respectively).
	MaxFrameSize   int
	MaxMessageSize int
}

UpgradeConfig tunes an Upgrade. The zero value is valid.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL