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.
Live.mount returns two nodes: the anchor <div> holding the first render, and
<script src="/eezo/live.js" defer>. The anchor's attributes:
| attribute | written by | value |
|---|---|---|
data-eezo-page |
the server, at mount | the page id, 32 hex characters |
data-eezo-base |
the server, at mount | /, rewritten by a mount to its prefix |
data-eezo-state |
the client | connecting, connected, lost |
data-eezo-dead |
the server, at mount | capacity, when the registry was full and this is a static render |
The client drives the first [data-eezo-page] it finds and no other.
| attribute | sent on | payload |
|---|---|---|
data-eezo-click |
click, default prevented | {} |
data-eezo-input |
input, after data-eezo-debounce ms of quiet, 300 by default |
{"value": ...} |
data-eezo-change |
change | {"value": ...} |
data-eezo-submit |
submit, default prevented | every 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.
| kind | fields | when |
|---|---|---|
join |
base |
first, after the socket opens |
event |
name, payload |
a binding fired |
ping |
every 30 seconds | |
failed |
reasons |
the 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.
| kind | fields |
|---|---|
patches |
patches, a list in application order |
error |
message |
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.
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.
| op | fields | effect |
|---|---|---|
setText |
text |
sets a text node's content; text is unescaped |
setAttr |
expect, name, value |
sets one attribute; a bare attribute is the empty string |
removeAttr |
expect, name |
removes one attribute |
replaceNode |
expect, html |
replaces the node at the path |
removeNode |
expect |
removes the node at the path |
appendChildren |
expect, html |
appends after the last child |
insertChild |
expect, index, html |
inserts at index, shifting what follows |
moveChild |
expect, from, to |
removes child from, inserts it at to in the shortened list |
setChildren |
expect, html |
replaces 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.
| code | sent by | meaning | the client then |
|---|---|---|---|
| 1000 | the client | beforeunload |
nothing; the server frees the page at once |
| 1001 | the server | the server is draining | retries |
| 1011 | the server | a send to this client failed | retries |
| 4403 | the server | origin not allowed, or not the page's user; the reason says which | logs the reason, stays lost, never reloads |
| 4404 | the server | unknown page: reaped or restarted | reloads into a fresh page |
| 4409 | the server | the page already has a socket, a duplicated tab | reloads into a fresh page |
| anything else, or no open | the server is unreachable | retries |
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.
| limit | value |
|---|---|
| pages per process | 10000; past it, a static render |
| never-connected page | reaped after 30 s |
| disconnected page, the grace window | reaped after 60 s |
| reaper cadence | every 10 s |
| mailbox per page | 256 topic deliveries; past it, dropped and resynced |
| slow event | logged after 10 s; the socket keeps waiting |
| text message | maxBodySize, 1 MiB |
| idle timeout | 5 minutes; the client pings every 30 s |
| outgoing frames queued | 64; a send that blocks longer than 10 s closes the connection |
Async work that throws |
logged; the state is unchanged |
The live layer is the model behind the protocol.