Sandbox APIs
rmx.* API reference
Every rmx call available to remixlet code, including signatures, capabilities, return values, limits, and worker security checks.
rmx is the sandbox API for extension services and runtime coordination. Calls that use browser privileges cross the sandbox host and run in the extension worker. Page access is provided separately by the dom.* API reference.
Security model#
Every worker call carries the remixlet id and a secret bridge token attached by the sandbox host. The token never enters remixlet code or the page. Before a call runs, the worker verifies:
- the bridge token belongs to the remixlet;
- the call came from a page covered by the remixlet's
matches; - neither the remixlet nor its site is paused;
- the manifest declares the required capability;
- the user approved that exact capability.
rmx.log is the only worker lane that bypasses the page and pause checks, so a paused or off-site caller can still record a diagnostic. It still requires a valid bridge token.
Capability denials reject with an Error and write the same message to the remixlet log. Argument validation failures reject without performing the operation.
Capability index#
| API | Required capability | Authority |
|---|---|---|
rmx.prefix | None | The filled mark prefix, for code written before the id slot. |
rmx.keep | None | Coordinates sandbox callbacks with page mutation notices. |
rmx.relay.on | See entry | Subscribes to low-level relay messages already filtered for this sandbox. |
rmx.navigation.onChange | None | Reports current-page URL changes. |
rmx.log.warn, rmx.log.error | None | Writes bounded diagnostic entries. |
rmx.storage.* | storage | Reads and writes this remixlet's extension-owned JSON store. |
rmx.fetch | fetch:<host-pattern> | Sends credentialed HTTP requests to approved hosts. |
rmx.network.onResponse | network:observe:<host-pattern> | Reads textual responses the page receives from approved hosts. |
rmx.notifications.* | notifications | Creates and clears owned text notifications. |
rmx.clipboard.writeText | clipboard | Writes text to the clipboard. |
rmx.menu.register | menu | Registers a command for the current tab and document. |
rmx.schedule.* | schedule | Stores fixed scheduled actions and site-open hooks. |
Runtime properties and callbacks#
rmx.prefix#
const prefix = rmx.prefix;Read-only string in the form rmx-<remixlet-id>. Attributes and classes added to page-owned elements carry this prefix on the page. New code does not read it: write marks as rmx--<name> (data-rmx--mix, rmx--on) and the page agent fills the id in, so the same spelling works in code, in style.css, and in verification selectors. Code written before the id slot interpolates rmx.prefix and still runs. See ownership marks.
rmx.keep#
const stop = rmx.keep(label, {
when,
ensure,
apply,
});Registers a condition that the runtime maintains and returns a synchronous unsubscribe function.
| Field | Type | Behavior |
|---|---|---|
label | string | Required, non-empty, trimmed to 120 characters for logging. |
when | () => value | Promise<value> | Optional. A falsey result idles this keep. |
ensure | () => value | Promise<value> | Required. A truthy result means no write is needed. |
apply | () => void | Promise<void> | Required. Runs when when passes and ensure is false. |
The runtime evaluates keeps at registration, after coalesced page mutations, after URL changes, and when the page removes a node listened to during the latest apply. Evaluations are serialized. A notice received during evaluation schedules one more pass.
After apply, the runtime runs ensure again. Three consecutive thrown callbacks or applies that leave ensure false halt the keep and write an error. At 25 successful reapplies within one minute, the runtime writes one informational entry and continues.
rmx.navigation.onChange#
const stop = rmx.navigation.onChange(({ url, previousUrl }) => {});Registers a callback and returns a synchronous unsubscribe function. The callback may be asynchronous. It runs only when the runtime's last known URL changes.
The page agent detects Navigation API changes, popstate, and hashchange. The extension worker and page relay provide fallback hints. Each hint causes a comparison against the last known URL, so forged or duplicate events cannot invent a URL change.
rmx.relay.on#
const stop = rmx.relay.on(topic, (data) => {});Registers a low-level relay callback and returns a synchronous unsubscribe function. The built-in relay emits network:response and navigation:change. Sequenced records buffered before registration are replayed oldest first. Unsequenced navigation hints are live only.
The page agent attaches a sandbox to the relay only when that remixlet has an approved network:observe:<host-pattern> capability. It forwards response records only when their URL matches one of the approved patterns. Relay payloads are untrusted page data and carry no bridge token or extension authority. Use rmx.network.onResponse and rmx.navigation.onChange for their typed filtering.
rmx.log.warn and rmx.log.error#
rmx.log.warn(message);
rmx.log.error(message);Writes a string to the per-remixlet log. Messages are capped at 500 characters. Each remixlet keeps at most 300 entries and 256 KB. All remixlet logs share a 3 MB cap. Eviction removes old log and info entries before warn and error entries.
The sandbox also captures console.log, console.info, console.warn, console.error, uncaught exceptions, unhandled rejections, bridge denials, and page-agent refusals. Console capture permits 100 lines per page load and 500 characters per line.
Storage#
Requires storage.
| Call | Returns | Description |
|---|---|---|
await rmx.storage.get(key) | JSON value or undefined | Reads one key. |
await rmx.storage.set(key, value) | void | Stores a JSON-compatible value. |
await rmx.storage.delete(key) | void | Removes one key. |
rmx.storage.watch(key, callback) | () => void | Long-polls for store revisions and calls back with the watched key's current value. |
The worker namespaces storage by remixlet id. All tabs and matched hosts for the same remixlet share it. Pages cannot access it. Values may contain strings, numbers, booleans, null, arrays, and plain objects.
watch reacts to any revision of the remixlet's store, including a change to another key. The callback runs only when the revision changes. If the service worker stops, the sandbox retries the long-poll after one second. The unsubscribe function stops future polls.
Deleting a remixlet removes its store. Serialized writes cannot recreate storage after deletion begins.
Fetch#
Requires an approved fetch:host capability that covers the initial URL and every redirect.
const response = await rmx.fetch(url, {
method,
headers,
body,
timeoutMs,
});The worker sends the request with the user's cookies for the target host. Page CORS rules do not apply.
| Input | Rule |
|---|---|
url | Absolute HTTP or HTTPS URL without embedded credentials. |
method | GET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS. |
headers | At most 32 KB. Browser-owned headers including cookie, origin, referer, user-agent, host, and content-length are refused. |
body | String, at most 1 MB. Refused for GET and HEAD. |
timeoutMs | Defaults to 15000. Maximum 30000. |
| redirects | Maximum five. Every target must match an approved fetch: pattern. Cross-origin redirects drop authorization. |
| response body | Text, at most 2 MB. A larger response rejects. |
An exact pattern covers one host. fetch:api.example.com does not cover www.example.com. fetch:*.example.com covers the apex and all subdomains.
The result is a frozen RmxResponse, not a browser Response:
| Member | Type |
|---|---|
url | Final URL string |
status | Number |
statusText | String |
ok | true for status 200 through 299 |
redirected | Boolean |
headers | Headers |
body | Response text |
await text() | Resolves to the response text |
await json() | Parses the response text as JSON |
The worker removes set-cookie and set-cookie2 from returned headers.
Network response observation#
Requires an approved network:observe:host capability. The exact and wildcard host grammar matches fetch:.
const stop = rmx.network.onResponse(hostPattern, (entry) => {});Registers a callback and returns a synchronous unsubscribe function. hostPattern may be an exact host, a *. host, an empty string for all approved hosts, or a URL substring that narrows the approved records.
| Entry field | Type | Description |
|---|---|---|
url | string | Absolute request URL. |
method | string | Uppercase request method. |
status | number | Response status. |
contentType | string | null | Response content type. |
body | string | Textual response body, capped at 512 KB. |
truncated | boolean | Whether the body exceeded the per-response cap. |
seq | number | Increasing sequence number for this page. |
The extension relay wraps page fetch and XMLHttpRequest at document start. It reads a clone and leaves the page's response unchanged. It records text, JSON, XML, and JavaScript response types. The ring holds at most 250 entries and 6 MB, evicting the oldest first. Buffered records are replayed before live records and deduplicated by seq.
The page agent receives approved host patterns from stored grants, not from the callback argument. It filters every record before sending it to the sandbox. A network:observe: grant does not permit rmx.fetch or admit new URLs through dom.*.
Notifications#
Requires notifications.
| Call | Returns | Description and limits |
|---|---|---|
await rmx.notifications.show(title, message) | Notification id | Creates a text notification. Title maximum: 120 characters. Message maximum: 1000 characters. Both must be non-empty strings. |
await rmx.notifications.clear(id) | boolean | Clears a notification created by this remixlet. |
Notification ids are unguessable handles. The worker verifies ownership before clearing one. The API has no icon, button, or URL fields.
Clipboard#
Requires clipboard.
await rmx.clipboard.writeText(text);Writes a string of at most 1 MB of UTF-8 to the clipboard. The API has no clipboard read operation.
Menu commands#
Requires menu.
const registrationId = await rmx.menu.register(id, label, callback);Registers a command in the Remixlet toolbar popup for the current tab and document. id may contain at most 128 characters. label may contain at most 160. Registering the same id again replaces the earlier command.
The worker stores command metadata and pending invocation ids. The callback remains inside the sandbox. A long-poll delivers clicks to the current document and acknowledges them after the callback settles. The command disappears when the document, tab, remixlet, grant, or matching active site no longer qualifies.
Schedules#
Requires schedule.
Timed schedules#
| Call | Returns | Description |
|---|---|---|
await rmx.schedule.register(definition) | Stored schedule | Registers { id, at, action } or { id, every, action }. |
await rmx.schedule.at(id, when, action) | Stored schedule | Registers a one-shot schedule. when is epoch milliseconds or an ISO date string. |
await rmx.schedule.every(id, minutes, action) | Stored schedule | Registers a repeating schedule. Minimum interval is 0.5 minutes on Chrome and 1 minute on Firefox. |
await rmx.schedule.remove(id) | boolean | Removes an owned timed schedule. |
await rmx.schedule.list() | { timed, onSiteOpen } | Lists timed definitions and registered site-open hook names. |
await rmx.schedule.clear() | void | Removes timed schedules, site-open hooks, queued hooks, session markers, and browser alarms. |
Ids and hook names may contain letters, digits, _, ., :, and -, must start with a letter or digit, and may contain at most 64 characters.
The worker stores data, not remixlet code. Each schedule uses one fixed action:
| Action | Effect and security |
|---|---|
{ type: "notify", title, message } | Shows an owned text notification with the notification length limits. |
{ type: "open", url } | Focuses an existing exact-URL tab or opens one. The URL must be absolute HTTP or HTTPS and covered by the remixlet's matches. |
{ type: "hook", name } | Queues a hook for the next matching page sandbox. |
register also accepts name as an alias for id, plus one shorthand action field: notify: { title, message }, open: url, open: { url }, or hook: name. Supplying action together with a shorthand action rejects as ambiguous.
Timers can fire without an open matching tab. One-shot schedules are removed after firing. Missed timers run once after browser startup; repeating schedules then move to their next future time. Pausing the remixlet stops its schedules until it resumes.
Site-open and hook delivery#
| Call | Returns | Description |
|---|---|---|
await rmx.schedule.onSiteOpen(name) | name | Registers a hook once per browser session when a matching site first opens. |
await rmx.schedule.onSiteOpen(name, callback) | () => void | Registers the hook and a callback, then delivers matching queued hooks. |
await rmx.schedule.removeOnSiteOpen(name) | boolean | Removes the registration and queued instances of that hook. |
await rmx.schedule.consumeHooks() | string[] | Atomically consumes all queued hook names for this remixlet. Only a matching page may consume them. |
rmx.schedule.onHook(name, callback) | () => void | Registers a callback and delivers queued instances of that name. |
Each hook callback receives { name }. A callback registered with onHook is delivered each matching queued instance once for that callback. The returned function unsubscribes it.
Compatibility#
dom.* and rmx.* share a versioned, add-only contract. Within one bridge version, methods and fields may be added but existing behavior is not renamed, removed, or changed.
The extension stamps the current bridge version into remixlet.json under builtWith.bridge. It parks a stored remixlet as needing repair when the stamp falls outside the installed extension's supported range. The current bridge version is 2.