Events & Stream — Observe everything
In v7.10.1, every request you send (e.g., navigate, domClick, runJs, getCurrentUrl) yields one unified response: RTO_RESPONSE with your requestId.
Firefox ✓ • Single response envelope • Designed for beginner-friendly logging & QA dashboards
v7.10.1
Heads-up: legacy
…Result event types (navigateResult, domActionResult, …) are superseded. Use ask(action, args) and listen for RTO_RESPONSE.
Quick glossary
- Request:
postMessagewith{ origin:"admin", type:"RTO_REQUEST", action:string, args:object, requestId:string } - Response:
{ origin:"extension", type:"RTO_RESPONSE", requestId, ok:boolean, data?:object, error?:{code,message} } - Where to listen:
window.addEventListener('message', handler)on your admin page. - Multi-tab: pass
tabKey/tabIdinargsto target a specific remote tab.
2) Unified response model
Each outgoing RTO_REQUEST produces one RTO_RESPONSE:
{
"origin": "extension",
"type": "RTO_RESPONSE",
"requestId": "r42",
"ok": true,
"data": { "url": "https://example.com/dashboard" } // shape depends on the action
}
Example: allow-list error
{
"origin": "extension",
"type": "RTO_RESPONSE",
"requestId": "r43",
"ok": false,
"error": { "code": "DOMAIN_NOT_ALLOWED", "message": "Host not allowed" }
}
Example: getCurrentUrl
{
"origin": "extension",
"type": "RTO_RESPONSE",
"requestId": "r44",
"ok": true,
"data": { "url": "https://app.example.com/dashboard" }
}
3) Common actions & response shapes
| Action (request) | Args (main) | data (on ok:true) | Typical error.code |
|---|---|---|---|
openTab |
{ url, focus?, tabKey? } |
{ url } |
INVALID_URL, DOMAIN_NOT_ALLOWED |
navigate |
{ url, focus?, tabKey? } |
{ url } |
INVALID_URL, DOMAIN_NOT_ALLOWED |
focus |
{ tabKey? } |
{ focused:true } |
— |
getCurrentUrl |
{ tabKey? } |
{ url?:string } |
NO_CONTROLLED_TAB |
getTitle |
{ tabKey? } |
{ title:string } |
NO_CONTROLLED_TAB |
domType/domSetValue/domClick/submitForm/waitFor… |
{ selector, …, tabKey? } |
{ ok:true } |
ELEMENT_NOT_FOUND, TIMEOUT, FORBIDDEN |
runJs |
{ code, tabKey? } |
any (returned as data) |
EXECUTION_ERROR |
Legacy mapping: if your app still expects
…Result events, migrate to the unified RTO_RESPONSE and read { ok, data, error }.
4) Subscribe & filter
Use the minimal helper to send requests; add a global listener to observe RTO_RESPONSE messages.
4.1 Minimal helper (copy-paste)
<script>
const ADMIN_ORIGIN='admin', EXT_ORIGIN='extension';
let _seq=0, _waiters=new Map();
addEventListener('message', (e) => {
const d=e.data; if(!d||typeof d!=='object') return;
if(d.origin!==EXT_ORIGIN) return;
if(d.type==='RTO_RESPONSE' && d.requestId && _waiters.has(d.requestId)){
_waiters.get(d.requestId)(d); _waiters.delete(d.requestId);
}
});
function ask(action, args={}, timeoutMs=12000){
const requestId='r'+(++_seq);
postMessage({ origin:ADMIN_ORIGIN, type:'RTO_REQUEST', requestId, action, args }, '*');
return new Promise((resolve,reject)=>{
const t=setTimeout(()=>{ _waiters.delete(requestId); reject(Object.assign(new Error('timeout'),{code:'TIMEOUT'})); }, timeoutMs);
_waiters.set(requestId, (resp)=>{ clearTimeout(t); resolve(resp); });
});
}
</script>
4.2 Subscribe with filters
<script>
const EXT_ORIGIN='extension';
const SUB = {
onOk(fn){ this._ok = fn; return this; },
onErr(fn){ this._err = fn; return this; },
any(fn){ this._any = fn; return this; }
};
addEventListener('message', e => {
const d=e.data; if(!d||d.origin!==EXT_ORIGIN||d.type!=='RTO_RESPONSE') return;
if (SUB._any) SUB._any(d);
if (d.ok) SUB._ok?.(d); else SUB._err?.(d);
});
// Example:
SUB.any(ev => console.log('[RTO]', ev.ok?'ok':'err', ev.requestId, ev));
SUB.onErr(ev => {/* centralized error handling */});
</script>
5) Live console widget (drop-in)
Paste this to visualize replies without DevTools. Toggle payloads for deeper inspection.
[waiting…]
Tip: Gate the widget with a query param (e.g.,
?debug=rto) or a feature flag in production.6) Metrics & durations
Measure latency per action and store simple KPIs for QA.
6.1 askLog wrapper (copy-paste)
<script>
const ADMIN_ORIGIN='admin';
function askLog(action, args={}, timeoutMs=12000){
const requestId='r'+Math.random().toString(36).slice(2);
const t0=performance.now();
postMessage({ origin:ADMIN_ORIGIN, type:'RTO_REQUEST', requestId, action, args }, '*');
return new Promise((resolve,reject)=>{
function onMsg(e){
const d=e.data; if(!d||d.type!=='RTO_RESPONSE'||d.requestId!==requestId) return;
removeEventListener('message', onMsg);
const dt=Math.round(performance.now()-t0);
(window.__rtoHist||(window.__rtoHist=[])).push({t:Date.now(), requestId, action, ok:d.ok, dt, error:d.error||null});
if(!d.ok) reject(Object.assign(new Error(d.error?.message||'error'), d.error||{})); else resolve({ ...d, dt });
}
addEventListener('message', onMsg);
setTimeout(()=>{ removeEventListener('message', onMsg); reject(Object.assign(new Error('timeout'),{code:'TIMEOUT'})); }, timeoutMs);
});
}
</script>
Persist window.__rtoHist in memory or sessionStorage, or export for QA.
7) Persist / export
- Short-term: rotate
sessionStorage['rto.events'](keep ~500 entries). - Export: JSON download to attach to QA tickets.
- PII safety: never log sensitive input values; protected fields are blocked by the extension anyway.
8) Security & privacy
- Reads/writes on sensitive fields are blocked (
FORBIDDEN), so secrets don’t appear in logs. - Respect the Allow-list (deny-by-default). On
DOMAIN_NOT_ALLOWED, guide the user and retry. - Redact payloads if you ship logs to your backend.
9) Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No responses at all | Extension not detected | Send a detect; show an install banner and reload same tab |
DOMAIN_NOT_ALLOWED |
Host not in Allow-list | Show the banner; user adds host in popup; retry |
Many ELEMENT_NOT_FOUND |
Brittle selectors / DOM not ready | Precede with waitFor; use stable [data-testid] |
Frequent TIMEOUT |
Slow SPA or selector mismatch | Increase timeoutMs, backoff, validate selector directly in page |
Next Pages
| Page | Description |
|---|---|
| Start | Overview, how it works, setup, quick start, and links to all sections. |
| Detect the Extension | Detection contract (ping/pong), install prompts, and graceful fallbacks when the extension isn’t present. |
| Allow-list & Permissions | Deny-by-default model, domain allow-list via popup, and suggested UX to guide users to enable hosts. |
| LAN / Localhost | How to safely enable intranet/localhost (flags + allow-list). |
| Remote Tab Control | Open/navigate/focus the controlled tab, read URL/title, select an existing tab, lifecycle & restore patterns. |
| DOM & Automation | Action catalog (type, setValue, click, select, submit, waitFor) and safe interaction tips. |
| Events | Event stream reference, live logger, and basic metrics/durations collection. |
| Diagnostics | Inline console, error cookbook, QA self-check, and performance tips. |
| Full API Reference | Canonical envelopes, request/response schemas, error codes, and return shapes. |
| Compatibility | Supported browsers/versions, CSP/iframe notes, known limitations/workarounds. |
| Advanced Patterns | Idempotent flows, retries/backoff, state restore, playlist strategies, robust selectors. |
| Favorites | Star/group URLs, quick actions, export/import sets, playlist runs from favorites. |
| Real-World Flows (E2E) | Run end-to-end flows from your admin page with best practices and troubleshooting. |
Need help writing safe flows?