WebSocket live receivers
SQSAPI-11.4c2 connects WebSocket on top of shared live sources and HTTP polling. Use MapLibre-style addSource/getSource and source.sqs; callers provide layer/button UI. Browser main, strict Worker, retained native QuickJS and the actual desktop script service run the same script. Static scene resources/playback/tracks, mobile hosts/devices and external Windows/macOS acceptance continue separately in the task list.
import {map} from 'sqsmap';
map.addSource('positions',{type:'geojson',data:{type:'FeatureCollection',features:[]},sqsLive:{fields:{n:'number'},allowDelta:true}});
await map.sqs.flush();
const source=await map.getSource('positions');
const receiver=await source.sqs.connectLive({protocol:'gis-live-v1',transport:{type:'websocket',url:'wss://your-server.example/live',reconnectDelayMs:2000}});
const state=await receiver.getState();
await receiver.pause();
await receiver.resume();
const frozen=await source.sqs.freezeLiveSnapshot();
await receiver.stop();
Replace the placeholder with your service. Each text message contains exactly one gis-live-v1 JSON batch; see the protocol. Every new connection, resume or reconnection requires a full snapshot first, then continuous deltas. A full snapshot may replay the final state, but cannot regress sequences or change data at the same sequence. Never convert decimal u128 sequences to JS numbers. Binary messages yield INVALID_ARGUMENT; ping/pong belongs to the transport.
connectLive confirms registration and queued I/O; open requires the first snapshot. Observe sourcedata/error. Independent SDK uses map.flush(), portable scripts map.sqs.flush(). getLiveReceiver returns the current handle or undefined. Identity/methods remain {id,sourceInstanceId,receiverInstanceId,getState,pause,resume,stop}; await methods. getState returns a small independent snapshot with source,receiverInstanceId,transport:'websocket',status,loading,intervalMs:0,lastError,dataRevision and connection:connecting/open/retrying/paused/stopped. intervalMs:0 means no polling, not zero-delay reconnection.
URLs are absolute WS/WSS with no embedded user/password or fragment; original and normalized addresses both have a4096byte limit. reconnectDelayMs defaults to2000, accepts500..60000ms and runs after prior termination with a fixed delay. WebSocket rejects intervalMs; HTTP rejects reconnectDelayMs. Unknown parameters are explicitly rejected. Each source has one receiver; each map allows8 active/paused receivers sharing8 source download slots.
Pause closes the real connection, cancels the old generation and retains committed data. Resume opens a new connection and requires a snapshot again. Stop retains data and releases active quota; resume then yields CONFLICT. Reconnect with connectLive for a new identity. Repeated pause/stop are safe. Source deletion/recreation or receiver replacement makes old handles STALE_HANDLE. Manual applyLiveUpdate conflicts while active or paused; freezeLiveSnapshot returns independent data. Ownership belongs to map/source and survives script/runner exit. Use stop/removeSource/map destruction; remove referencing layers before deleting a source.
Connection/schema/continuity/cluster/display failure retains committed data and protocol, records a typed error, terminates that generation and reconnects requiring a complete snapshot. Missing deltas are never invented. Ready-stream duplicate batches change no revisions/cluster identity and publish no data-change event; the first replayed snapshot on a fresh connection still confirms readiness. Connection/reconnection emits sourcedataloading; success sourcedata; pause/stop sourcedataabort; failure error. Historical sqsLiveReceiver/sqsLive, nested identity/errors are immutable and omit URLs, credentials and bodies. Late frames cannot write to replaced sources.
Per-message8MiB; each map's waiting data queue allows8 frames/16MiB raw bytes, plus one zero-payload terminal record per connection, at most8. Event/output budget failures retain the sole response and its reservation. FIFO never coalesces deltas. Transport overflow yields RESOURCE_LIMIT, closes and resynchronizes instead of silently dropping deltas while continuing. Shared4096point/32field and source budgets still apply. These queue limits do not bound browser network buffers, temporary parsing/prepare copies or total process memory.
Native browser WebSocket cannot set arbitrary HTTP authorization headers. For this transport, transformRequest only accepts synchronous {url}. Headers/method/cache and similar parameters explicitly yield CAPABILITY_UNAVAILABLE. A configured apiToken for the site's API WebSocket is also rejected. Use an endpoint-supported authorization scheme with access restrictions; sensitive values do not enter state/errors. HTTPS pages require WSS; servers must validate Origin and permit the required protocol/network access. Transformed URLs still follow the shared URL policy. Native C hosts implement networking and authorization themselves.
Browser capability: map.capabilities.liveWebSocket. Native flag SQSM_MAP_LIVE_WEBSOCKET=536870912; ABI1/34functions unchanged. Same six source JSON methods sqs.connectLive/getLiveReceiver/liveState/pauseLive/resumeLive/stopLive; C results/control shapes are in the HTTP guide. Resource requests mark transport:'websocket',format:'gis-live-v1'. Acknowledge once, submit successive frames with the same generation/id. Submit a typed terminal error through the existing response channel. Pump requests/events/responses on the map creation thread and honor repaintAfterMs. Hosts must close cancelled sockets. Core owns no network library/background network thread. Linux desktop reuses tungstenite, browsers share MapRuntime; C acceptance transport is only a loopback fixture, not a production networking SDK.
The complete HTML/CSS includes all five handlers, events, attribution and pagehide destruction. Serve it beside the matched SDK and enter your reachable endpoint. Download complete HTML and the portable acceptance script. Loopback3133 fixture reproduces acceptance and is not a public data service.
source.sqs.getLiveState() separately returns the shared protocol snapshot {initialized,epoch,sequence,protocolRevision,pointCount,allowDelta,fields}. protocolRevision tracks protocol commits; receiver.dataRevision tracks source data. Connection readiness and duplicates do not imply either revision advanced. C resource accepted=1 also includes acknowledged replay frames. A ready WebSocket retains its resource generation even when pending-resource count is0; do not terminate it early. Cancellation/termination, source deletion or map destruction ends ownership.
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>SqsMap live WebSocket</title>
<style>body{margin:0;font:16px system-ui}#map{height:70vh}form{display:flex;flex-wrap:wrap;gap:.5rem;padding:1rem}input{flex:1;min-width:16rem}button,input{font:inherit;padding:.4rem}pre,footer{padding:0 1rem;white-space:pre-wrap}</style>
<div id="map"></div><form id="tools"><input id="url" type="url" required placeholder="Your gis-live-v1 WebSocket endpoint"><button id="connect">Connect</button><button type="button" id="pause">Pause</button><button type="button" id="resume">Resume</button><button type="button" id="stop">Stop</button><button type="button" id="freeze">Freeze snapshot</button></form><pre id="status">Enter your WebSocket endpoint.</pre><footer id="credits"></footer>
<script type="module">
import {MapView} from './dist/index.js';
const status=document.querySelector('#status');let map,source,receiver;const offs=[];
async function act(fn){try{await fn();}catch(error){status.textContent=String(error);}}
try{
map=await MapView.create(document.querySelector('#map'),{worker:false});if(!map.capabilities.liveWebSocket)throw Error('Matched live WebSocket SDK/WASM required');
map.jumpTo({center:[0,0],zoom:12});map.addSource('positions',{type:'geojson',data:{type:'FeatureCollection',features:[]},sqsLive:{fields:{n:'number'},allowDelta:true},attribution:'Live positions supplied by the endpoint owner'});map.addLayer({id:'positions',type:'circle',source:'positions',paint:{'circle-radius':8,'circle-color':'#ff0000'}});await map.flush();source=await map.getSource('positions');
for(const type of ['sourcedataloading','sourcedata','sourcedataabort','error'])offs.push(map.on(type,event=>{if(event.sourceId==='positions')status.textContent=JSON.stringify({type,protocol:event.sqsLive,receiver:event.sqsLiveReceiver},null,2);}));
offs.push(map.on('sqs.attributionschange',event=>{document.querySelector('#credits').textContent=event.attributions.text;}));document.querySelector('#credits').textContent=(await map.getAttributions()).text;
document.querySelector('#tools').onsubmit=event=>{event.preventDefault();act(async()=>{if(receiver&&(await receiver.getState()).status!=='stopped')await receiver.stop();receiver=await source.sqs.connectLive({transport:{type:'websocket',url:document.querySelector('#url').value,reconnectDelayMs:1000}});});};
document.querySelector('#pause').onclick=()=>act(async()=>{if(!receiver)throw Error('Connect first.');await receiver.pause();});
document.querySelector('#resume').onclick=()=>act(async()=>{if(!receiver)throw Error('Connect first.');await receiver.resume();});
document.querySelector('#stop').onclick=()=>act(async()=>{if(!receiver)throw Error('Connect first.');await receiver.stop();});
document.querySelector('#freeze').onclick=()=>act(async()=>{status.textContent=JSON.stringify(await source.sqs.freezeLiveSnapshot(),null,2);});
window.addEventListener('pagehide',()=>{offs.forEach(off=>off());map.destroy();},{once:true});
}catch(error){status.textContent=String(error);offs.forEach(off=>off());map?.destroy();}
</script></html>
Evidence lives in tools/live-ws-contract: the same32check script on four hosts, plus browser real connections, four rendered frames, binary/oversized/first-delta errors, duplicate events and ownership after script exit. Software Vulkan/Gl does not replace hardware WebGPU, external desktop or mobile-device acceptance. Development snapshots are not formal npm publication or remote deployment.
Shared scenes and receiver ownership provides draft/start/pause/resume/stop and historical events; static GeoJSON ownership is available in static scenes; playback, tracks and mobile acceptance continue separately.
Static scene resources and layer visibility covers same-source layers, draft hiding, controls, queries/credits and complete HTML.