Messaging protocol

How the page and the extension communicate (you usually do not need to write this by hand).
Docs updated: 2026-03-24

Overview

RTO uses window.postMessage to exchange messages between your page and the extension. The helper RTO_form_api.js handles:

  • Generating requestId values
  • Waiting for the matching *Result message
  • Timeouts (TIMEOUT)
Recommendation
Use RTO_form_api.js unless you are building your own wrapper.

Modern allow-list flow

In 7.13.0, a page should ask through allowlistAddRequest. The plugin then prompts on the matching master tab, and the master tab answers with either allowlistAddConfirm or allowlistAddReject.

Message Direction Role
allowlistAddRequest page → extension Ask for a host to be added from a normal page.
promptAllowlistAdd extension → master tab Visible prompt sent to the master tab UI.
allowlistAddConfirm master tab → extension Approve the pending request.
allowlistAddReject master tab → extension Reject the pending request.
allowlistAddRequestResult extension → page Final success or failure returned to the requesting page.
Important
Direct page-side allowlistAdd is no longer the normal public flow. It is intentionally blocked unless the plugin/internal UI performs the confirmation step.

Message envelope

Outgoing messages usually contain:

  • origin: a page-side marker (docs default: window.RTO_PAGE_ORIGIN)
  • type: for example "detect", "openTab", or "command"
  • requestId: unique ID to match replies

The extension responds with type + "Result" (example: detectResult, commandResult).

Important
The origin value is a protocol marker, not a security boundary. Security is enforced by the extension (allow-list + controlled tabs). For raw code, always filter messages strictly (see examples).

Raw detect example

// Raw detect (without RTO_form_api.js)
(function(){
  const requestId = "detect_" + Date.now() + "_" + Math.random().toString(16).slice(2);
  const pageOrigin = window.RTO_PAGE_ORIGIN || "rto-docs";
  let t;

  function done(){
    if(t) clearTimeout(t);
    window.removeEventListener("message", onMsg);
  }

  function onMsg(ev){
    const d = ev.data;
    if(ev.source !== window) return;                 // same window only
    if(!d || typeof d !== "object") return;
    if(d.origin !== pageOrigin) return;              // protocol marker must match
    if(d.requestId !== requestId) return;
    if(d.type !== "detectResult") return;

    done();
    console.log("detect result:", d);
  }

  window.addEventListener("message", onMsg);

  t = setTimeout(function(){
    done();
    console.warn("detect timeout");
  }, 1500);

  window.postMessage({
    origin: pageOrigin,
    type: "detect",
    requestId: requestId
  }, "*");
})();

Raw command example

DOM actions are sent as type:"command" with an action name and a payload object.

// Raw command (example: getText)
(function(){
  const requestId = "cmd_" + Date.now() + "_" + Math.random().toString(16).slice(2);
  const pageOrigin = window.RTO_PAGE_ORIGIN || "rto-docs";
  let t;

  function done(){
    if(t) clearTimeout(t);
    window.removeEventListener("message", onMsg);
  }

  function onMsg(ev){
    const d = ev.data;
    if(ev.source !== window) return;
    if(!d || typeof d !== "object") return;
    if(d.origin !== pageOrigin) return;
    if(d.requestId !== requestId) return;
    if(d.type !== "commandResult") return;

    done();
    console.log("command result:", d);
  }

  window.addEventListener("message", onMsg);

  t = setTimeout(function(){
    done();
    console.warn("command timeout");
  }, 5000);

  window.postMessage({
    origin: pageOrigin,
    type: "command",
    requestId: requestId,
    action: "getText",
    payload: {
      tabKey: "demoTab",
      selector: "h1"
    }
  }, "*");
})();
Note
Even if you write raw messages, you still need allow-list permission and a controlled tab for most actions.

Next