Proxy e Isolamento
O Web Host executa cada micro frontend filho em um contexto sandboxed e o conecta ao host através da API do Proxy. Apps micro frontend e web components alcançam o host importando de @wippy-fe/proxy.
A API do Proxy
A API do Proxy é seu ponto de entrada para o host. Um runtime — proxy.js — a entrega: ele coloca a API e o AppConfig atual na página e os expõe através do módulo @wippy-fe/proxy.
- Para um app micro frontend (
view.page), o host injetaproxy.jsnosrcdocda página. - Para um web component (
view.component), o runtime já está presente na página do host — o componente monta no DOM do host, não em um iframe separado.
Seu código o consome através dos getters síncronos exportados por @wippy-fe/proxy:
import { host, api, on, config } from '@wippy-fe/proxy'
host.navigate('/dashboard')
const data = await api.get('/api/v1/agents') // api é uma instância axios; o await é a chamada HTTP
on('@visibility', (visible) => { /* pausa ou retoma o trabalho */ })
O roteamento Vue portável é a exceção: @wippy-fe/router consome @history e reporta a navegação local por você. Não adicione inscrições manuais de roteamento em torno dele.
Esses getters são síncronos: host, api, on, config e os demais estão prontos no momento em que seu código roda — a configuração já está no lugar antes de o runtime inicializar (veja abaixo), então não há handshake a aguardar. Marque @wippy-fe/proxy como external no seu build Vite — o host o fornece através do import map. Veja API do Proxy para a superfície completa.
Como a configuração chega ao iframe de um app
Quando o host carrega uma view.page, ele constrói um srcdoc e injeta, nesta ordem, antes do script do seu app:
<!-- 1. O AppConfig do filho — definido de forma síncrona, antes de o runtime carregar -->
<script>window.__WIPPY_APP_CONFIG__ = { /* auth, env, theming, hostConfig, context */ }</script>
<!-- 2. As flags de injeção de CSS para esta página -->
<script>window.__WIPPY_PROXY_CONFIG__ = { injections: { css: { themeConfig: true, primevue: true /* … */ } } }</script>
<!-- 3. O runtime (precedido por loading.js) -->
<script src="/.../loading.js"></script>
<script src="/.../proxy.js"></script>
Como o global de configuração é definido antes de proxy.js rodar, o runtime inicializa de forma síncrona e os getters de @wippy-fe/proxy funcionam imediatamente — sem handshake. As páginas não referenciam esses scripts diretamente; o placeholder <script data-role="@wippy/scripts"> é substituído pelo host pelas tags corretas e ordenadas. Sobrescritas por página chegam como window.__WIPPY_CONFIG_OVERRIDES__ (veja API do Proxy — Sobrescritas de configuração).
Um web component vê os mesmos globais porque roda na página do host, onde o runtime já os definiu antes de o connectedCallback do componente disparar.
Como apps e web components diferem
Ambos importam a mesma API de @wippy-fe/proxy. Eles diferem no contexto de execução e em como os estilos são entregues:
App Micro Frontend (view.page) |
Web Component (view.component) |
|
|---|---|---|
| Roda em | seu próprio iframe srcdoc |
o DOM da página do host (Shadow DOM) |
| Entrega do runtime | proxy.js injetado no iframe |
runtime já presente na página do host |
| CSS | pipeline completo de injeção (themeConfig, primevue, …) — veja Injeção de CSS |
hostCssKeys no Shadow DOM — veja Tematização: Web Components |
Composição e aninhamento
Filhos se compõem. Um app micro frontend ou um web component pode, ele mesmo, hospedar filhos — novamente apps micro frontend ou web components — que podem hospedar os seus próprios, em qualquer profundidade. Cada nível usa a mesma API @wippy-fe/proxy.
Como um nó hospeda um filho depende do tipo do filho:
- Um filho em iframe — um app micro frontend, um artefato ou HTML arbitrário do Wippy — passa por
<w-iframe>,<w-artifact>ouhtml.inject. Estes injetam o runtime (URL base, import map,loading.js,proxy.jse configuração) nosrcdocdo filho, de modo que ele obtém a API do Proxy exatamente como um app de nível superior. Seu proxy faz a ponte para cima, através do pai, até o host. - Um filho web component não precisa de nada disso. Renderize sua tag — ou carregue-a com
loadWebComponent/loadByTagName— e ele roda no mesmo DOM, importando a API do Proxy diretamente.
O próprio código do filho é idêntico, esteja ele rodando no nível superior ou aninhado em vários níveis: importe de @wippy-fe/proxy e use. Não há regras especiais de aninhamento.
Veja <w-iframe>, <w-artifact> e Injeção Avançada de HTML abaixo para os mecanismos.
Internals — não leia nem sobrescreva
O proxy.js instala os seguintes globais para uso próprio. Código de aplicação e de componente nunca deve lê-los ou atribuí-los — use @wippy-fe/proxy. Eles são documentados apenas para que você não os sobrescreva acidentalmente:
| Global | O que é |
|---|---|
window.$W |
Objeto acessor assíncrono ($W.host(), $W.api(), …). Interno; @wippy-fe/proxy é a superfície suportada. |
window.getWippyApi / window.initWippyApi |
Funções assíncronas de "resolver a instância". Internas (initWippyApi está obsoleta). |
window.__WIPPY_APP_API__ |
A instância de proxy resolvida. |
window.__WIPPY_APP_CONFIG__ |
O snapshot do AppConfig do filho. |
window.__WIPPY_PROXY_CONFIG__ / window.__WIPPY_CONFIG_OVERRIDES__ |
Flags de injeção de CSS e sobrescritas por página. |
window.__WIPPY_WEB_COMPONENT_CACHE__ |
Cache de componentes carregados. |
Dois pontos de entrada compõem a API pública de JavaScript: initWippyApp(config, rootContainer?) monta todo o Web Host (a entrada de module-embed que a facade usa; veja Ponto de Entrada da Facade), e @wippy-fe/proxy é a API síncrona para apps e componentes filhos. Tudo na tabela acima é interno.
Protocolo PostMessage (IFrameMessageType) — transporte interno
Este é o protocolo de fio que o runtime usa internamente; código de aplicação nunca envia nem recebe essas mensagens — @wippy-fe/proxy as trata por você.
O caminho padrão injetado pelo host não precisa de handshake para iniciar — a configuração já está presente de forma síncrona como window.__WIPPY_APP_CONFIG__ antes de proxy.js rodar, então o runtime constrói sua instância imediatamente. A troca get-config/set-config ainda acontece nesse caminho, mas apenas como um canal não bloqueante de ressincronização e atualização ao vivo: depois que a instância síncrona é construída, o runtime do iframe sempre envia get-config, o host responde com set-config, e reenvia set-config a cada atualização posterior da configuração. Filhos <w-iframe> aninhados se comportam da mesma forma. Seu código nunca espera por nada disso — os getters síncronos já estão ativos.
O handshake é a única fonte de configuração, e bloqueante, em exatamente um cenário: o embedding manual de iframe sem facade (iframe.html?waitForCustomConfig), onde não há um window.__WIPPY_APP_CONFIG__ pré-injetado, então a inicialização bloqueia no primeiro set-config e o pai deve responder à requisição get-config (veja Ponto de Entrada da Facade § Embedding manual em iframe).
Toda mensagem é um envelope JSON no formato { type: '@gen2-chat', action: IFrameMessageType.*, ...payload }. O campo type é configurável via APP_CONFIG_IFRAME_EVENT_TYPE, mas tem '@gen2-chat' como padrão.
Todos os tipos de mensagem estão definidos no enum IFrameMessageType:
| Membro do enum | Valor no fio | Direção | Descrição |
|---|---|---|---|
GetConfig |
get-config |
Filho → Host | Handshake inicial: o filho solicita seu AppConfig |
SetConfig |
set-config |
Host → Filho | O host entrega o AppConfig em resposta a GetConfig |
UrlWasUpdatedInParent |
url-was-updated-in-parent |
Host → Filho | A URL do host mudou; dispara o evento @history do filho |
VisibilityWasUpdatedInParent |
visibility-was-updated-in-parent |
Host → Filho | A visibilidade do iframe mudou; dispara o evento @visibility do filho |
TopicWasReceivedInParent |
topic-was-received-in-parent |
Host → Filho | Entrega um evento de tópico WebSocket aos filhos inscritos |
CmdRouteChanged |
cmd-route-changed |
Filho → Host | A rota interna do filho mudou; o host atualiza a URL do navegador |
CmdTitleChanged |
cmd-title-changed |
Filho → Host | O document.title do filho mudou; o host atualiza o título da página |
CmdStartChat |
cmd-start-chat |
Filho → Host | Abre uma nova sessão de chat |
CmdOpenSession |
cmd-open-session |
Filho → Host | Navega para uma sessão de chat existente |
CmdOpenArtifact |
cmd-open-artifact |
Filho → Host | Abre um artefato na barra lateral ou em modal |
CmdNavigate |
cmd-navigate |
Filho → Host | Requisição de navegação SPA |
CmdShowToast |
cmd-show-toast |
Filho → Host | Exibe uma notificação toast |
CmdShowConfirm |
cmd-show-confirm |
Filho → Host | Exibe um diálogo de confirmação |
OnConfirmResult |
on-confirm-result |
Host → Filho | Entrega o resultado do diálogo de confirmação |
CmdSetContext |
cmd-set-context |
Filho → Host | Envia contexto para uma sessão de chat |
CmdHandleError |
cmd-handle-error |
Filho → Host | Reporta um erro ao host |
CmdLogout |
cmd-logout |
Filho → Host | Dispara o logout |
CmdSubscribe |
cmd-subscribe |
Filho → Host | Inscreve-se em um tópico WebSocket |
CmdUnSubscribe |
cmd-unsubscribe |
Filho → Host | Cancela a inscrição em um tópico |
OnSubscription |
on-subscription |
Host → Filho | Entrega dados de evento de inscrição |
CmdStateGet |
cmd-state-get |
Filho → Host | Lê uma chave de estado persistido |
CmdStateSet |
cmd-state-set |
Filho → Host | Escreve uma chave de estado persistido |
CmdStateRemove |
cmd-state-remove |
Filho → Host | Remove uma chave de estado persistido |
CmdStateClear |
cmd-state-clear |
Filho → Host | Limpa todo o estado desta página |
CmdStateGetAll |
cmd-state-get-all |
Filho → Host | Lê todo o estado persistido |
OnStateResult |
on-state-result |
Host → Filho | Entrega o resultado da leitura de estado |
OnStateError |
on-state-error |
Host → Filho | Reporta falha em operação de estado |
CmdWsSend |
cmd-ws-send |
Filho → Host | Encaminha um comando WebSocket pela conexão do host |
CmdBodySize |
cmd-body-size |
Filho → Host | Reporta o tamanho do body para auto-height |
CmdBridgePost |
cmd-bridge-post |
Filho ↔ Pai | Mensagem de canal sem retorno via host.bridge |
CmdBridgeRequest |
cmd-bridge-request |
Filho ↔ Pai | Mensagem de canal requisição/resposta via host.bridge |
CmdClaimNavOwner |
cmd-claim-nav-owner |
Filho → Host | Reivindica a propriedade da navegação (modo nav-owner) |
CmdReleaseNavOwner |
cmd-release-nav-owner |
Filho → Host | Libera a propriedade da navegação |
CmdLayoutSubscribe |
cmd-layout-subscribe |
Filho → Host | Inscreve-se em atualizações de layout gerenciado |
CmdLayoutUpdatePanel |
cmd-layout-update-panel |
Filho → Host | Aplica patch a uma definição de painel |
CmdLayoutBroadcast |
cmd-layout-broadcast |
Filho ↔ Host | Mensagem do barramento de layout dentro da aba |
OnLayoutChange |
on-layout-change |
Host → Filho | Atualização completa do snapshot de layout |
OnLayoutPanelChanged |
on-layout-panel-changed |
Host → Filho | Delta de estado ao vivo por painel |
OnLayoutBroadcast |
on-layout-broadcast |
Host → Filho | Entrega de broadcast do barramento de layout |
Código de aplicação nunca envia nem recebe essas mensagens diretamente. O proxy trata o protocolo de forma transparente e expõe apenas a superfície de API @wippy-fe/proxy.
Elemento Customizado <w-iframe>
<w-iframe> é a primitiva de iframe de baixo nível embutida no proxy.js. Ele aceita HTML de origem bruto, injeta o runtime completo do Wippy (URL base, import map, loading.js, proxy.js, configuração do filho) e renderiza o resultado como um iframe srcdoc sandboxed.
Use <w-iframe> quando você tem HTML de origem e quer o mesmo comportamento de runtime que os apps micro frontend do Wippy obtêm automaticamente: API autenticada, relay de estado, relay de WebSocket, roteamento nav-owner e mensageria de ponte pai-filho.
Atributos e propriedades
| Atributo / propriedade | Obrigatório | Padrão | Descrição |
|---|---|---|---|
src |
Não | — | URL a buscar como HTML de origem bruto através do api do proxy. |
srcdoc |
Não | — | HTML de origem bruto. Também pode ser definido como element.srcdoc = html para strings grandes. |
base-url |
Não | Derivado de src ou document.baseURI |
<base href> injetado para resolução de assets relativos. |
resource-id |
Não | id do elemento, depois src |
Identificador de contexto do filho; define o escopo padrão de estado e de log. |
resource-type |
Não | page |
Tipo de contexto do filho: page ou artifact. |
sub-path |
Não | Rota do pai | Rota inicial do filho. Encaminhada como config.context.route no handshake GetConfig. |
auto-height |
Não | false |
Redimensiona a altura do iframe para corresponder aos relatos CmdBodySize do filho. |
nav-owner |
Não | false |
Intercepta o CmdRouteChanged do filho e despacha eventos DOM nav-owner-route em vez de alterar a URL do host. |
Propriedades JS aceitas no elemento:
const frame = document.querySelector('w-iframe')
frame.proxyConfig = { injections: { css: { markdown: false } } }
frame.configOverrides = { customization: { customCSS: ':root { --brand: red }' } }
frame.srcdoc = sourceHtml
Eventos e métodos
| Evento | Detail | Descrição |
|---|---|---|
loading |
— | Disparado antes de iniciar fetch/processamento/renderização. |
load |
— | Disparado depois que o iframe sandbox carrega. |
error |
Erro original | Disparado quando o fetch, a injeção ou o carregamento falha. |
nav-owner-route |
{ path: string, navId?: number } |
Mudança de rota do filho quando nav-owner está definido. O evento faz bubbling e é composed. |
wippy-message |
{ channel, payload, requestId?, respond?, reject? } |
Mensagem de ponte vinda do filho. |
| Método | Descrição |
|---|---|
post(channel, payload?) |
Mensagem de ponte sem retorno para o filho. |
request<T>(channel, payload?, { timeoutMs }?) |
Mensagem de ponte requisição/resposta; resolve com o valor retornado pelo handler. |
Shadow parts: loader, error, frame.
Quando nav-owner está definido, o ciclo padrão de sincronização de rota é totalmente suprimido: o host não atualiza sua própria barra de URL e não posta UrlWasUpdatedInParent de volta ao filho. A propriedade da navegação é delegada inteiramente ao código pai que escuta nav-owner-route. O path no detail do evento é a rota interna bruta do filho, exatamente como o filho a passou a host.onRouteChanged(internalRoute, navId?) — ela não tem prefixo de mount (diferente do caminho padrão de CmdRouteChanged, em que o host prefixa o mount da página). O pai que faz o embedding é responsável por qualquer prefixação ou mapeamento de router:
const frame = document.querySelector('w-iframe')
frame.addEventListener('nav-owner-route', (event) => {
const { path, navId } = event.detail
myRouter.push(path)
})
Ponte pai-filho
A ponte usa canais nomeados, para que nenhum dos lados precise de envelopes postMessage brutos.
Lado do pai:
const frame = document.querySelector('w-iframe')
frame.addEventListener('wippy-message', async (event) => {
const { channel, payload, respond, reject } = event.detail
if (channel === 'pick-file') {
try {
respond({ id: 'file-1', name: 'data.csv' })
} catch (error) {
reject(error)
}
}
})
frame.post('refresh', { reason: 'parent-click' })
const result = await frame.request('get-selection', undefined, { timeoutMs: 5000 })
Lado do filho:
import { host } from '@wippy-fe/proxy'
host.bridge.post('ready', { value: 1 })
const file = await host.bridge.request('pick-file', { accept: '.csv' })
const off = host.bridge.on('refresh', async (payload) => {
console.log('refresh requested', payload)
return { ok: true }
})
host.bridge.on() retorna uma função de cancelamento de inscrição (() => void). Um canal = um handler ativo. Se múltiplos handlers forem registrados para o mesmo canal, o registrado mais recentemente vence e trata todas as mensagens que chegam nesse canal — tanto post() sem retorno quanto request(). on() não é aditivo: handlers anteriores são sombreados (não removidos) e não rodam enquanto existir um handler mais novo, e o proxy registra um console.warn em caso de registro duplicado. Se o handler mais novo cancelar sua inscrição, o handler anterior daquele canal volta a ficar ativo. Use nomes de canal distintos se você precisar de múltiplos listeners independentes.
Se você omitir options.timeoutMs, host.bridge.request() (e o frame.request() do lado do pai) usam por padrão um prazo de 10 segundos (10000 ms). No timeout, a Promise retornada é rejeitada com um Error cuja mensagem é Bridge request <id> timed out after <ms>ms. Uma requisição a um canal para o qual o outro lado não tem handler é rejeitada imediatamente com No handler registered for channel "<channel>", em vez de aguardar o prazo.
Elemento Customizado <w-artifact>
<w-artifact> resolve metadados e conteúdo de artefato ou página e, em seguida, delega internamente os tipos baseados em iframe a <w-iframe>. Ele trata da detecção de tipo de conteúdo (HTML, Markdown, pacotes de página web, pacotes ESM, componentes de tag direta) e fornece uma API de nível mais alto que o <w-iframe> bruto.
Atributos
| Atributo | Obrigatório | Valores | Padrão | Descrição |
|---|---|---|---|---|
id |
Sim | UUID de artefato / página | — | Identificador do conteúdo. |
type |
Não | artifact | page |
artifact |
Determina o endpoint REST chamado: /api/v1/artifact/<id>/content ou /api/public/pages/content/<id>. |
auto-height |
Não | flag booleana | false |
Encaminhado ao <w-iframe> interno para sincronização de altura via CmdBodySize. |
url |
Não | Qualquer URL | — | Busca o conteúdo diretamente desta URL; ignora id/type. |
sub-path |
Não | String de caminho | — | Encaminhado ao <w-iframe> interno como rota inicial do filho. |
nav-owner |
Não | flag booleana | false |
Encaminhado ao <w-iframe> interno; mudanças de rota do filho despacham nav-owner-route. |
Eventos
| Evento | Quando | Detail |
|---|---|---|
loading |
Antes de o fetch começar | — |
load |
Depois que o iframe carrega | — |
error |
O fetch ou a renderização falha | Erro original |
nav-owner-route |
Mudança de rota do filho em modo nav-owner | { path: string, navId?: number } |
wippy-message |
Mensagem de ponte do iframe aninhado | { channel, payload, requestId?, respond?, reject? } |
Status e parts de CSS
O elemento define um atributo status (loading, ready, error) e expõe shadow parts:
w-artifact[status="loading"] { opacity: 0.5; }
w-artifact[status="error"] { border: 1px solid var(--p-danger-color); }
w-artifact::part(loader) { font-size: 1rem; }
w-artifact::part(frame) { border: 0; }
<w-iframe> vs <w-artifact> vs <iframe> bruto
| Recurso | <w-iframe> |
<w-artifact> |
<iframe> bruto |
|---|---|---|---|
| Injeta o runtime do Wippy | Sim | Sim (via <w-iframe>) |
Não |
| Resolve metadados de artefato/página | Não | Sim | Não |
| Busca autenticada de conteúdo | Sim (HTML bruto) | Sim (resolver completo) | Não |
| Relay de estado | Sim | Sim | Não |
| Relay de WebSocket | Sim | Sim | Não |
| Ponte pai-filho | Sim | Sim (encaminhada) | Não |
| Suporte a nav-owner | Sim | Sim | Não |
| Detecção de tipo de conteúdo | Não | Sim | Não |
| Shadow parts de CSS | loader, error, frame |
loader, error, frame |
— |
Atributo status |
Sim | Sim | Não |
Use <w-artifact> quando você tem um UUID de artefato do Wippy ou um ID de página e quer que a plataforma cuide de toda a resolução. Use <w-iframe> quando você já tem o HTML de origem e quer injeção direta do runtime. Use um <iframe> bruto apenas para conteúdo completamente externo que não precisa da API do Wippy.
Injeção Avançada de HTML
Para casos em que você precisa da transformação de HTML de origem para srcdoc sem montar um elemento, o proxy expõe html.inject(...):
import { html } from '@wippy-fe/proxy'
const processed = await html.inject(sourceHtml, {
baseUrl: 'https://example.com/app/',
resourceId: 'child-id',
resourceType: 'page',
route: '/initial',
})
A mesma função é acessível como instance.html.inject, $W.html e import { html } from '@wippy-fe/proxy'. Prefira <w-iframe> para montagem normal; use html.inject(...) apenas ao construir infraestrutura de hospedagem customizada.