Skip to main content

Native script custom protocols

Supply native map resource bytes directly from retained QuickJS callbacks

Development native libraries built with script expose named addProtocol and removeProtocol imports from sqsmap through retained sessions. nativeScriptProtocols in await map.sqs.getCapabilities() is true only on this connected host. Low-level C maps, one-shot native scripts, WASM and application common scripts do not automatically provide these callbacks. The browser SDK retains its existing registration in the actual main-thread/map-Worker resource realm.

import {map, addProtocol, removeProtocol} from 'sqsmap';
// This sample JSON contains ASCII characters only.
const text = JSON.stringify({type: 'FeatureCollection', features: [
  {type: 'Feature', id: 1, properties: {},
   geometry: {type: 'Point', coordinates: [116.4, 39.9]}}
]});
const bytes = new Uint8Array(Array.from(text, c => c.charCodeAt(0)));
addProtocol('local', (request, controller) => {
  controller.signal.throwIfAborted();
  if (request.url !== 'local://sample/point.json') throw Error('Unknown resource');
  return {data: bytes};
});
const ready = new Promise((resolve, reject) => {
  let loaded, failed;
  const close = () => {loaded.unsubscribe(); failed.unsubscribe();};
  loaded = map.on('sourcedata', event => {
    if (event.sourceId === 'sample' && event.isSourceLoaded) {close(); resolve();}
  });
  failed = map.on('error', event => {
    if (event.sourceId === 'sample') {close(); reject(Error(event.error.message));}
  });
});
map.addSource('sample', {type: 'geojson', data: 'local://sample/point.json'});
await map.sqs.flush();
await ready;
map.addLayer({id: 'points', type: 'circle', source: 'sample',
  paint: {'circle-radius': 8, 'circle-color': 'red'}});
map.jumpTo({center: [116.4, 39.9], zoom: 12});
await map.sqs.flush({render: true});
removeProtocol('local');

Registrations belong to the script session: lowercase URI schemes, up to32 characters and16 schemes. HTTP/HTTPS/WS/WSS/file/data/blob remain host transports. One active session owns each map/scheme; a conflicting session fails rather than replacing another owner's handler. Re-registering in the same session cancels its earlier pending callbacks.

Handlers receive immutable url, resourceType, maxBytes and method fields plus a cancellation controller. Return {data: ArrayBuffer | Uint8Array} directly or asynchronously; only the valid range of a subview is copied. Existing decoders handle UTF-8 JSON, images, glyph PBF, tiles and video. Currently only data is accepted; unsupported cache metadata or extra result fields fail explicitly.

Concurrency is the configured value capped at8. Total timeout follows resourceTimeoutMs (1–120000ms). A response must fit both its original resource budget and the8MiB response limit. Queued replies are capped at16MiB/32 entries before copying. Each pump retains the2-second/4096-job budget. Callbacks may await map queries. No network client, extra threads, DOM, fetch or timers are added; handlers obtain or generate their own bytes.

Removal, replacement, resource invalidation and timeout cancel pending controllers; old-generation bytes are never reused. Explicit stop releases the VM without executing user cleanup, fails outstanding resources and releases ownership for later sessions. Map removal retains the existing final remove cleanup, and retained handles can still be stopped/released afterward. Handler exceptions, invalid bytes and timeout become resource loading errors; awaited loadImage can catch IO_ERROR.

C hosts keep pumping sqsm_map_script_session_pump_json. Registered resources are excluded from the external sqsm_map_resource_requests_json queue, while unregistered resources remain available. C output ownership, ABI1 and40 exports are unchanged.

Local acceptance covers12 actual C/QuickJS resource checks with video pixels, four ownership/stop/timeout/destroy checks, ten native VM regressions and two documentation compilations. Acceptance script. Android/iOS devices, the desktop default downloader and cross-Worker application script callback forwarding remain separate tasks.