Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// Pai envia uma mensagem para um iframe
const iframeRef = useRef<HTMLIFrameElement>(null);
function sendToIframe(data: unknown) {
iframeRef.current?.contentWindow?.postMessage(
data,
"https://widget.example.com" // SEMPRE especifique a origem de destino
);
}
// Iframe (ou qualquer janela) escuta mensagens
useEffect(() => {
function handleMessage(event: MessageEvent) {
// CRÍTICO: sempre valide a origem
if (event.origin !== "https://parent.example.com") return;
console.log("Recebido:", event.data);
}
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);Quando usar isso: Quando você precisa de comunicação entre uma página pai e um iframe incorporado, entre janelas abertas com window.open, ou entre quaisquer dois contextos de navegação que não podem compartilhar o escopo JavaScript diretamente. Casos de uso comuns incluem micro-frontends, widgets de terceiros incorporados, formulários de pagamento, popups OAuth e fluxos de autenticação entre origens.
Uma página pai envia um objeto de tema para um iframe incorporado. O iframe aplica o tema e envia de volta um reconhecimento.
import { useEffect, useRef, useState } from "react";
// -- Protocolo de mensagem tipado usando uniões discriminadas --
type ParentMessage =
| { type: "THEME_UPDATE"; payload: Theme }
| { type: "PING" };
type IframeMessage =
| { type: "THEME_ACK"; payload: { appliedAt: number } }
| { type: "PONG" };
interface Theme {
mode: "light" | "dark";
primaryColor: string;
fontSize: number;
}
const IFRAME_ORIGIN = "https://widget.example.com";
function ParentApp() {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [acked, setAcked] = useState(false);
// Escuta respostas do iframe
useEffect(() => {
function handleMessage(event: MessageEvent<IframeMessage>) {
if (event.origin !== IFRAME_ORIGIN) return;
switch (event.data.type) {
case "THEME_ACK":
console.log("Tema aplicado em:", event.data.payload.appliedAt);
setAcked(true);
break;
case "PONG":
console.log("Iframe está ativo");
break;
}
}
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
function sendTheme(mode: "light" | "dark") {
const message: ParentMessage = {
type: "THEME_UPDATE",
payload: { mode, primaryColor: "#3b82f6", fontSize: 16 },
};
iframeRef.current?.contentWindow?.postMessage(message, IFRAME_ORIGIN);
setAcked(false);
}
return (
<div>
<h1>App Pai</h1>
<button onClick={() => sendTheme("light")}>Tema Claro</button>
<button onClick={() => sendTheme("dark")}>Tema Escuro</button>
{acked && <p>Tema reconhecido pelo widget</p>}
<iframe
ref={iframeRef}
src={`${IFRAME_ORIGIN}/widget`}
title="Widget Incorporado"
style={{ width: 600, height: 400, border: "1px solid #ccc" }}
/>
</div>
);
}import { useEffect, useState } from "react";
interface Theme {
mode: "light" | "dark";
primaryColor: string;
fontSize: number;
}
type ParentMessage =
| { type: "THEME_UPDATE"; payload: Theme }
| { type: "PING" };
type IframeMessage =
| { type: "THEME_ACK"; payload: { appliedAt: number } }
| { type: "PONG" };
const PARENT_ORIGIN = "https://parent.example.com";
function Widget() {
const [theme, setTheme] = useState<Theme>({
mode: "light",
primaryColor: "#3b82f6",
fontSize: 16,
});
useEffect(() => {
function handleMessage(event: MessageEvent<ParentMessage>) {
// CRÍTICO: valide a origem antes de processar
if (event.origin !== PARENT_ORIGIN) return;
switch (event.data.type) {
case "THEME_UPDATE":
setTheme(event.data.payload);
// Envia reconhecimento de volta para o pai
const ack: IframeMessage = {
type: "THEME_ACK",
payload: { appliedAt: Date.now() },
};
// event.source é a janela que enviou a mensagem
(event.source as Window).postMessage(ack, event.origin);
break;
case "PING":
(event.source as Window).postMessage(
{ type: "PONG" } satisfies IframeMessage,
event.origin
);
break;
}
}
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
return (
<div
style={{
backgroundColor: theme.mode === "dark" ? "#1e293b" : "#ffffff",
color: theme.mode === "dark" ? "#f1f5f9" : "#0f172a",
fontSize: theme.fontSize,
padding: 24,
minHeight: "100vh",
}}
>
<h2 style={{ color: theme.primaryColor }}>Conteúdo do Widget</h2>
<p>Tema atual: {theme.mode}</p>
</div>
);
}window.postMessage() é a API padrão do navegador para comunicação segura entre origens entre contextos de navegação (janelas, iframes, popups, workers).targetWindow.postMessage(message, targetOrigin, transfer?).Todo manipulador de mensagens recebe um MessageEvent com estas propriedades chave:
| Propriedade | Tipo | Descrição |
|---|---|---|
data | any | A carga útil da mensagem (clone estruturado do que foi enviado) |
origin | string | A origem da janela remetente (por exemplo, https://example.com) |
source | Window or MessagePort or ServiceWorker or null | Referência à janela remetente; use para responder |
ports | ReadonlyArray<MessagePort> | Portas do MessageChannel transferidas com a mensagem |
lastEventId | string | String vazia para postMessage (usado por SSE) |
postMessage(data, targetOrigin) no objeto da janela de destino (não na sua própria).targetOrigin. Se não corresponder, a mensagem é silenciosamente descartada.Date, Map, Set, ArrayBuffer, Blob, File, RegExp, arrays tipados e objetos aninhados. Ele não suporta funções, nós DOM, Symbol, ou WeakMap/WeakSet."message" da janela receptora dispara com um MessageEvent contendo os dados clonados, a origem do remetente e uma referência à janela do remetente.event.origin em todo manipulador de mensagens. Sem essa verificação, qualquer página que possa incorporar sua página (ou que sua página incorpore) pode enviar mensagens arbitrárias para seu manipulador.targetOrigin ao chamar postMessage. Usar "*" significa que qualquer origem pode receber a mensagem. Isso é perigoso quando a mensagem contém dados sensíveis (tokens, informações do usuário, etc.). Veja o documento de segurança para detalhes completos.// Padrão useEffect limpo para listeners de postMessage
useEffect(() => {
const ALLOWED_ORIGINS = new Set([
"https://widget-a.example.com",
"https://widget-b.example.com",
]);
function handleMessage(event: MessageEvent) {
if (!ALLOWED_ORIGINS.has(event.origin)) return;
// Estreita o tipo com base na união discriminada
const msg = event.data;
if (typeof msg !== "object" || msg === null || !("type" in msg)) return;
switch (msg.type) {
case "THEME_UPDATE":
// lidar com...
break;
// ...
}
}
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []); // Dependências vazias: listener é estável, sem closures obsoletas necessárias// Define todas as mensagens em uma união. O campo "type" é o discriminante.
type AppMessage =
| { type: "NAVIGATE"; payload: { path: string } }
| { type: "AUTH_TOKEN"; payload: { token: string; expiresAt: number } }
| { type: "RESIZE"; payload: { width: number; height: number } }
| { type: "READY" }; // nenhum payload necessário
// Type guard para validar dados de entrada desconhecidos
function isAppMessage(data: unknown): data is AppMessage {
if (typeof data !== "object" || data === null) return false;
if (!("type" in data)) return false;
const d = data as { type: string };
return ["NAVIGATE", "AUTH_TOKEN", "RESIZE", "READY"].includes(d.type);
}
// Uso no manipulador
function handleMessage(event: MessageEvent) {
if (event.origin !== EXPECTED_ORIGIN) return;
if (!isAppMessage(event.data)) return;
// TypeScript agora estreita corretamente em cada caso
switch (event.data.type) {
case "AUTH_TOKEN":
// event.data.payload é { token: string; expiresAt: number }
setToken(event.data.payload.token);
break;
case "NAVIGATE":
// event.data.payload é { path: string }
router.push(event.data.payload.path);
break;
}
}event.origin, mesmo em desenvolvimento. Crie o hábito cedo."*" como origem de destino transmite para qualquer origem. Use-o apenas para mensagens verdadeiramente públicas e não sensíveis (e mesmo assim, pense duas vezes).postMessage não retorna um valor. Se você precisar de uma resposta, deve implementar um protocolo de solicitação/resposta com IDs de mensagem (abordado no guia intermediário).event.source pode ser nulo se a janela remetente foi fechada antes que seu manipulador dispare. Sempre verifique se há nulo antes de responder.contentWindow antes que o evento load do iframe dispare falhará silenciosamente. Use o callback onLoad do iframe ou espere por uma mensagem READY do iframe.type da mensagem para rotear para o manipulador correto.Date, Map, Set, ArrayBuffer, Blob, etc., mas lançará um erro em funções e nós DOM. Se você precisar verificar o que é clonável, use structuredClone() localmente para testar.| Abordagem | Quando Usar |
|---|---|
| BroadcastChannel | Guias ou janelas de mesma origem que precisam compartilhar estado (sem suporte a cross-origin) |
| Channel Messaging (MessageChannel) | Porta dedicada de via dupla entre exatamente dois contextos (abordado no guia avançado) |
| SharedWorker | Múltiplos guias de mesma origem compartilhando um único worker para coordenação de estado |
| Custom Events | Comunicação dentro do mesmo documento (sem cross-origin, sem cross-frame) |
| Server-Sent Events ou WebSocket | Quando você precisa de comunicação mediada por servidor entre clientes |
| Fragmento de URL ou parâmetros de consulta | Passagem de dados única para um iframe (sem comunicação contínua) |
Date, Map, Set, ArrayBuffer, Blob, File e RegExp.Symbol, WeakMap e WeakSet não são suportados e lançarão um DataCloneError.Set de origens conhecidas e válidas para validação rápida e correspondência exata."*" apenas para mensagens verdadeiramente públicas e não sensíveis.type AppMessage =
| { type: "NAVIGATE"; payload: { path: string } }
| { type: "READY" };
function handleMessage(event: MessageEvent<AppMessage>) {
if (event.origin !== EXPECTED_ORIGIN) return;
switch (event.data.type) {
case "NAVIGATE":
// event.data.payload é { path: string }
break;
}
}function isAppMessage(data: unknown): data is AppMessage {
if (typeof data !== "object" || data === null) return false;
if (!("type" in data)) return false;
const d = data as { type: string };
return ["NAVIGATE", "READY"].includes(d.type);
}contentWindow não está pronto.onLoad do iframe ou espere por uma mensagem READY do iframe antes de enviar.postMessage não retorna um valor. Se você precisar de uma resposta, deve construir um protocolo de solicitação/resposta com IDs de correlação.Set é uma correspondência exata de string, que é mais segura e mais rápida.Date, Map, Set, ArrayBuffer, Blob, referências circulares e mais.Date se torna uma string, Map se torna {}).JSON.stringify antes de enviar ou JSON.parse ao receber.event.source é null.event.source é nulo antes de chamar postMessage nele para responder.useEffect(() => {
function handleMessage(event: MessageEvent) {
if (event.origin !== EXPECTED) return;
// lidar com a mensagem
}
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);Como Enviar Dados de iframe para Página Pai - Tutorial JavaScript postMessage
Protocolo Window postMessage() usando React - #1 Tutoriais JavaScript
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥