Skip to content

@ws

Open a WebSocket and route its messages like a server event stream (htmx’s ws extension). Incoming messages use the same wire format as text/event-stream responses, so patch-signals, patch-elements and your own handlers work unchanged; outgoing messages go through send.

$socket = @ws('/live')
$socket = @ws('wss://example.com/live', { protocols: ['v1'] })
$socket.send({ type: 'chat', text: $draft }) // objects are JSON-encoded
$socket.send('ping') // strings are sent as they are
$socket.close()

The URL is resolved against the page and http(s) becomes ws(s). The returned object has send, close and the raw socket; store it in a signal to use it from other elements. The socket closes when the element that opened it unmounts.

Each message is one or more blank-line-separated blocks of field: value lines, exactly like an event stream:

event: sigmx-patch-signals
data: signals {"online": 12}
event: sigmx-patch-elements
data: selector #chat
data: mode append
data: elements <li>hello</li>

Every block with at least one data line is dispatched as sigmx-server-event and handed to the handler matching its event name (prefix stripped). Blocks without data lines are ignored.

This site has no socket server, so the demo below drives the same code path with a stand-in socket object to show what a message does once it arrives.

what an incoming block does

Given the message on the left, the page changes on the right:

event: sigmx-patch-signals
      data: signals {"online": 12}

      event: sigmx-patch-elements
      data: selector #ws-chat
      data: mode append
      data: elements <li>hello from the socket</li>
online:
  • …
<div data-signals="{ draft: '', online: 0 }" data-init="$sock = @ws('/chat')">
<p><span data-text="$online"></span> online</p>
<ul id="chat"></ul>
<form data-on:submit="$sock.send({ text: $draft }); $draft = ''">
<input data-bind:draft> <button>Send</button>
</form>
</div>

The server answers each message with blocks that patch #chat and online; nothing on the client parses JSON or touches the DOM.

The repository’s dev server has a /ws echo endpoint exercised by the browser test suite. Reconnection is not built in: open a new socket from data-on:close-style logic on the raw socket if the connection drops.

import { websocket } from 'sigmx/plugins'

Registering websocket adds 394 bytes brotli (463 gzip, 999 minified) on top of the core. See bundle sizes.