ReferenceEdit on GitHub

The live wire protocol#

What travels between a live page and the server: the anchor's attributes, the frames in both directions, the patch kinds, the close codes and the limits. Component, Live, Topic and Async are in the API.

The anchor#

Live.mount returns two nodes: the anchor <div> holding the first render, and <script src="/eezo/live.js" defer>. The anchor's attributes:

attributewritten byvalue
data-eezo-pagethe server, at mountthe page id, 32 hex characters
data-eezo-basethe server, at mount/, rewritten by a mount to its prefix
data-eezo-statethe clientconnecting, connected, lost
data-eezo-deadthe server, at mountcapacity, when the registry was full and this is a static render

The client drives the first [data-eezo-page] it finds and no other.

Event bindings#

attributesent onpayload
data-eezo-clickclick, default prevented{}
data-eezo-inputinput, after data-eezo-debounce ms of quiet, 300 by default{"value": ...}
data-eezo-changechange{"value": ...}
data-eezo-submitsubmit, default preventedevery named field, {name: value, ...}

The value of a checkbox or radio is on when ticked and empty otherwise; anything else sends its value. A submit payload follows FormData: ticked checkboxes as on, the selected option's value, files skipped. Listeners are delegated at the document, so a binding survives any patch that replaces its element.

data-eezo-ignore on an element keeps the differ out of its children. data-eezo-key on every child of a list makes the list keyed.

Client to server#

kindfieldswhen
joinbasefirst, after the socket opens
eventname, payloada binding fired
pingevery 30 seconds
failedreasonsthe applier refused patches

base is validated to /[A-Za-z0-9_./-]*, at most 200 characters, and normalised; anything else is ignored and logged. An event's name is a non-empty string, its payload an object of at most 64 string entries. Up to 8 reasons of up to 500 characters each are kept. A frame that fails any of this is answered with an error frame and the page carries on.

Server to client#

kindfields
patchespatches, a list in application order
errormessage
pong

A resync is a patches frame with one setChildren whose path is [] and expect is null: the anchor's whole content replaced. The server sends one on every join, on a mailbox overflow, and after a failed report.

Patch kinds#

Every patch has op and path, a list of child indices from the anchor, so the component's root is [0]. expect is the node name the server believes is at the path, #text for text, and the client refuses a patch whose target disagrees.

opfieldseffect
setTexttextsets a text node's content; text is unescaped
setAttrexpect, name, valuesets one attribute; a bare attribute is the empty string
removeAttrexpect, nameremoves one attribute
replaceNodeexpect, htmlreplaces the node at the path
removeNodeexpectremoves the node at the path
appendChildrenexpect, htmlappends after the last child
insertChildexpect, index, htmlinserts at index, shifting what follows
moveChildexpect, from, toremoves child from, inserts it at to in the shortened list
setChildrenexpect, htmlreplaces every child

Within one positional list a frame holds in-place updates, then removals highest index first, then one append. Within one keyed list: removals highest index first, then moves, then inserts ascending, then content patches at final positions.

Close codes#

codesent bymeaningthe client then
1000the clientbeforeunloadnothing; the server frees the page at once
1001the serverthe server is drainingretries
1011the servera send to this client failedretries
4403the serverorigin not allowed, or not the page's user; the reason says whichlogs the reason, stays lost, never reloads
4404the serverunknown page: reaped or restartedreloads into a fresh page
4409the serverthe page already has a socket, a duplicated tabreloads into a fresh page
anything else, or no openthe server is unreachableretries

A retry waits 250 × 2^(n-1) ms, capped at 3000. A connect attempt that neither opens nor closes within 4 seconds is closed and counted as a retry. A reload is suppressed when the last one was under 3 seconds ago without a patches frame in between, so a cached page can't loop.

Limits#

limitvalue
pages per process10000; past it, a static render
never-connected pagereaped after 30 s
disconnected page, the grace windowreaped after 60 s
reaper cadenceevery 10 s
mailbox per page256 topic deliveries; past it, dropped and resynced
slow eventlogged after 10 s; the socket keeps waiting
text messagemaxBodySize, 1 MiB
idle timeout5 minutes; the client pings every 30 s
outgoing frames queued64; a send that blocks longer than 10 s closes the connection
Async work that throwslogged; the state is unchanged

The live layer is the model behind the protocol.