Server-Sent Events
PageloveSSE is the live mutation streaming client: it opens a Server-Sent Events connection to a document, parses incoming HTML Microdata mutation events, applies them to the live DOM, and re-dispatches them as cancelable custom events on document.
When to reach for it
Reach for it when a view should update in real time as the underlying document changes — collaborative editing, live dashboards, multi-tab synchronization — without polling. For the default case, no application code is required at all: importing the module is enough.
Loading the library
Add the module to the page:
<script type="module" src="https://pagelove.github.io/beta-js/pagelove/sse.mjs"></script>
Importing pagelove/sse.mjs auto-instantiates one PageloveSSE subscribed to the current page URL. The default instance is held internally and begins streaming as soon as the module evaluates. Application code is only needed when subscribing to a different URL or when intercepting events.
The PageloveSSE class
import { PageloveSSE } from 'https://pagelove.github.io/beta-js/pagelove/sse.mjs';
Constructors
| Form | Subscription target |
|---|---|
new PageloveSSE() |
window.location.href — the current page |
new PageloveSSE(url) |
The given document URL |
The constructor opens the underlying EventSource immediately, with withCredentials: true so cookies and authorization travel with the request.
Methods
| Method | Description |
|---|---|
close() |
Close the underlying EventSource and drop the reference. The instance cannot be reopened — construct a new one to resubscribe. |
Properties
| Property | Type | Description |
|---|---|---|
url |
string |
The URL this client is subscribed to. Read-only. |
source |
EventSource | null |
The underlying EventSource, or null after close(). Read-only. |
Static methods
| Method | Description |
|---|---|
PageloveSSE.parse(data) |
Parse an HTML Microdata mutation payload. Returns { method, selector, path, host, body, etag } or null. |
PageloveSSE.parseReset(data) |
Parse a reset payload. Returns the reason string, or "unknown" if no reason is present. |
DOM mutations
When a mutation event arrives, PageloveSSE locates the target with the carried CSS selector and applies the change:
| Method | Action |
|---|---|
POST |
Appends the new content as a child of the matched element. |
PUT |
Replaces the matched element with the new content. |
DELETE |
Removes the matched element. |
If no element matches the selector, the event is silently dropped. Mutations that echo a local write (matched against the pending PLMethodStarted queue) are recognized as echoes and the DOM step is skipped — the local change already produced the result.
This local echo check only reconciles the same tab's own optimistic write against the mutation event that write eventually produces — it does not affect which events the server delivers. PageloveSSE connects with a plain EventSource and does not currently read the server-assigned connection token or send it back on writes (see Echo suppression), so the server falls back to its session-based suppression: a write from one browser tab is not delivered live to another tab of the same session, since same-origin tabs share the session cookie. Each tab only ever sees its own writes reflected through this local echo-matching step, not through the SSE stream. If your application needs one tab to see another same-session tab's live writes, subscribe with your own EventSource, listen for the pagelove-connection event to capture the server-assigned token, and send it back as a Pagelove-Connection header on every mutating request from that tab, as described in that section, rather than relying on the default PageloveSSE connection.
Events on document
Three custom events are dispatched on document. All three bubble and are composed.
| Event | When it fires | Cancelable | event.detail contains |
|---|---|---|---|
PLMutation |
Before a parsed mutation is applied to the DOM | yes — preventDefault() skips the DOM change |
method, selector, path, host, body, etag, element |
PLMutationApplied |
After the mutation has been applied | no | Same shape as PLMutation. element is the new or modified node, or null for DELETE. |
PLStreamReset |
When the server sends a reset event | yes — preventDefault() suppresses the default location.reload() |
reason |
PLMutation detail fields:
| Field | Type | Description |
|---|---|---|
method |
string |
HTTP method — POST, PUT, or DELETE. |
selector |
string |
CSS selector of the target element. |
body |
string |
HTML fragment to apply. Empty for DELETE. |
path |
string |
Document path the mutation belongs to. |
host |
string |
Virtual host the mutation belongs to. |
etag |
string |
ETag of the resulting element, when supplied. |
element |
Element |
The DOM element matched by the selector. |
Listening for events
document.addEventListener('PLMutation', (event) => {
const { method, selector, element } = event.detail;
console.log(`incoming ${method} ${selector}`);
// event.preventDefault(); // would skip the DOM change
});
PLMutationApplied follows the same shape and fires after the change has landed. PLStreamReset carries event.detail.reason; calling preventDefault() cancels the automatic reload.
Focus protection
Focus protection — deferring incoming changes that target the currently focused element until focus leaves — is not implemented in pagelove/sse.mjs. It lives in the Pagelove class, which observes the schema, queues patches against focused inputs, and exposes flushPendingPatches() and hasPendingPatches for explicit control.
Examples
Default — include the script
<script type="module" src="https://pagelove.github.io/beta-js/pagelove/sse.mjs"></script>
The page is now live. Any mutation the server streams for this URL is applied to the DOM as it arrives.
Manual subscription to a different URL
<script type="module">
import { PageloveSSE } from 'https://pagelove.github.io/beta-js/pagelove/sse.mjs';
const sidebar = new PageloveSSE('/sidebar.html');
document.addEventListener('PLMutationApplied', (event) => {
if (event.detail.path === '/sidebar.html') {
console.log('sidebar updated', event.detail.selector);
}
});
// Later, when the sidebar is dismissed:
// sidebar.close();
</script>
A second subscription is opened against /sidebar.html alongside the auto-instantiated default. Both feed events to the same document listeners; consumers distinguish them by event.detail.path.
See also
- The Pagelove class — focus-aware patch queueing and the rest of the runtime.
- Server-Sent Events (HTTP reference) — the wire protocol that
PageloveSSEconsumes.