Plugin / Docs / Events & Stream

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: postMessage with { 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/tabId in args to 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
Need help writing safe flows?
Try the ChatGPT helper: Remote Tab Opener Copilot.

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 / LocalhostHow 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.