Ponto de Entrada da Facade
O módulo de backend wippy/facade é o ponto de entrada que entrega o Web Host aos usuários. Ele serve uma página HTML que carrega o módulo JS do Web Host, trata redirecionamentos de autenticação, expõe um endpoint /facade/config e leva a configuração específica do deployment para o bundle de frontend hospedado em CDN. Nenhuma configuração é gravada no próprio bundle — cada deployment fornece sua própria configuração através desse mecanismo.
A Página HTML
Quando um usuário navega até uma aplicação Wippy, o wippy/facade serve uma página HTML. Essa página é fina: ela carrega um módulo JS do Web Host da CDN e inicializa o host com a configuração retornada por /facade/config. O módulo assume a página inteira — incluindo seu histórico de navegador — de modo que o host roda como a aplicação toda, e não dentro de um iframe.
A facade carrega uma de duas entradas de módulo JS, dependendo do fe_mode configurado:
module.js— o shell compat (padrão): o layout padrão de barra de navegação + área de página + painel direito de chat.managed-layout.js— o shell managed (adesão opcional, acesso antecipado): o layout multi-painel declarativo.
Uma versão simplificada da página é assim:
const configResponse = await fetch('/api/public/facade/config')
if (!configResponse.ok)
throw new Error('Facade config request failed: ' + configResponse.status)
const cfg = await configResponse.json()
const storedAuth = localStorage.getItem('@wippy_token_info')
if (!storedAuth)
throw new Error('Authentication is required before bootstrapping the host')
const { token } = JSON.parse(storedAuth)
if (typeof token !== 'string' || token.length === 0)
throw new Error('Stored authentication does not contain a token')
const mapResponse = await fetch(cfg.facade_url + '/import-map.json')
if (!mapResponse.ok)
throw new Error('Host import map request failed: ' + mapResponse.status)
const hostMap = await mapResponse.json()
await new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = cfg.facade_url + '/@wippy-fe/import-map-bootstrap.js'
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
window.WippyImportMapBootstrap.register(undefined, hostMap, cfg.importMap, document.baseURI)
await import(cfg.facade_url + cfg.module_file)
window.initWippyApp({
$schema: cfg.schema_url || (cfg.facade_url + '/schemas/wippy-context-2.0.xsd'),
auth: { token, expiresAt: new Date(Date.now() + 86_400_000).toISOString() },
env: cfg.env,
routePrefix: cfg.routePrefix,
themeMode: window.wippyThemePersist?.read() || cfg.themeMode,
apiRoutes: cfg.apiRoutes,
attention: cfg.attention,
allowSelectModel: cfg.allowSelectModel,
hideSessionSelector: cfg.hideSessionSelector,
allowAdditionalTags: cfg.allowAdditionalTags,
axiosDefaults: cfg.axiosDefaults,
iconify: cfg.iconify,
importMap: cfg.importMap,
theming: cfg.theming,
hostConfig: cfg.hostConfig,
context: { resourceId: '', resourceType: 'page' },
}, '#app')
A página busca sua configuração e a entrega à função de init do módulo. O host monta na página, assume o roteamento e o histórico do navegador, e prossegue com a inicialização completa.
Nota sobre o caminho do fetch.
/facade/configé o caminho que a facade registra no router público; a URL efetivamente buscada pela sua página inclui o prefixo desse router. Com o prefixo de exemplo/api/public, ela é/api/public/facade/config— exatamente o que a página de facade entregue busca. Os trechosfetch('/facade/config')aqui estão encurtados para facilitar a leitura.
O Fluxo de Configuração
O fluxo de configuração tem duas etapas:
-
A página busca
/facade/configno router público da mesma origem. -
O shell busca o import map do Host, carrega o bootstrap compartilhado e registra o mapa do Host combinado com
cfg.importMap. Só então importacfg.module_filedinamicamente. O shell montaAppConfigcom os campos retornados e adiciona o schemawippy-context-2.2,authecontextantes de chamarinitWippyApp.
O Web Host extrai o payload AppConfig do objeto de configuração e prossegue com a inicialização completa. A partir desse ponto, o script da página é passivo — toda interação do usuário acontece dentro do host montado.
Esse padrão significa que o bundle hospedado em CDN nunca contém URLs, tokens ou branding específicos do deployment. O bundle é idêntico para todo deployment. Apenas o payload de configuração difere.
O endpoint retorna configurações do shell e campos selecionados do Web Host. Ele não retorna um AppConfig completo: o shell adiciona $schema, auth e context antes de chamar initWippyApp. O Facade 0.6.46 fornece schema_url: wippy-context-2.0.xsd para a política legada e wippy-context-2.1.json para a política moderna. Se um Facade mais antigo não incluir esse campo, o shell usa o fallback XSD 2.0. O Web Host 1.0.66 aceita ambos os schemas versionados e os migra para o formato canônico 2.2.
A Resposta de /facade/config
O endpoint retorna configurações do shell e campos selecionados do Web Host. Ele não retorna um AppConfig completo: o shell adiciona $schema, auth e context antes de chamar initWippyApp. O Facade 0.6.46 fornece schema_url: wippy-context-2.0.xsd para a política legada e wippy-context-2.1.json para a política moderna. Se um Facade mais antigo não incluir esse campo, o shell usa o fallback XSD 2.0. O Web Host 1.0.66 aceita ambos os schemas versionados e os migra para o formato canônico 2.2.
{
"facade_url": "https://web-host.wippy.ai/<release-tag>",
"iframe_origin": "https://web-host.wippy.ai",
"iframe_url": "https://web-host.wippy.ai/<release-tag>/iframe.html?waitForCustomConfig",
"login_path": "/login.html",
"login_redirect_param": "return_to",
"mode": "compat",
"module_file": "/module.js",
"env": {
"APP_API_URL": "https://api.example.com",
"APP_AUTH_API_URL": "https://api.example.com",
"APP_WEBSOCKET_URL": "wss://api.example.com"
},
"routePrefix": "https://api.example.com",
"themeMode": "auto",
"themePersist": "localStorage",
"themeStorageKey": "@wippy-theme-mode",
"axiosDefaults": { "timeout": 30000 },
"apiRoutes": { "agents": { "list": "/custom/agents" } },
"tanstack": { "lists": { "refetchOnWindowFocus": true } },
"iconify": {},
"importMap": {
"imports": { "example-package": "https://cdn.example.com/example-package.js" }
},
"extraScripts": ["/monitoring.js"],
"theming": {},
"hostConfig": {}
}
Referência de Campos
Campos de nível de shell — consumidos pela página de embedding para construir a si mesma; não fazem parte do AppConfig do filho:
| Campo | Descrição |
|---|---|
facade_url |
URL base de CDN para o bundle do Web Host. Usada para resolver a entrada do módulo e os scripts de vendor. |
iframe_origin |
Valor do header Origin da CDN. Usado como targetOrigin para PostMessage em embeddings manuais de iframe (veja abaixo). |
iframe_url |
src completo do iframe, incluindo ?waitForCustomConfig. Usado apenas por embeddings manuais de iframe sem facade (veja abaixo). |
login_path |
Caminho na origem da página para onde redirecionar usuários não autenticados. |
Campos do AppConfig do filho — passados à função de init do host e consumidos pelo host em execução:
| Campo | Descrição |
|---|---|
$schema |
cfg.schema_url; se ausente: <facade_url>/schemas/wippy-context-2.0.xsd |
auth |
Bearer token de runtime e expiração, injetados como AppConfig.auth. |
env |
URLs de runtime injetadas como AppConfig.env de nível superior. |
routePrefix |
Prefixo de URL de API encaminhado aos apps filhos. |
axiosDefaults |
Padrões da instância Axios encaminhados aos apps filhos. |
apiRoutes |
Sobrescreve caminhos individuais de endpoints de API (campo de nível superior do AppConfig). |
tanstack |
Padrões do TanStack Query — global + por categoria baseada em papel (content/lists); campo de nível superior do AppConfig. O padrão do host é refetchOnWindowFocus:false. |
iconify |
Fontes Iconify configuradas explicitamente e encaminhadas de cfg.iconify. |
importMap |
Extensão opcional do import map do navegador de cfg.importMap; o shell a registra antes de importar módulos do Host. |
theming |
Customização de CSS dividida em três escopos. |
hostConfig |
Feature flags e configuração de UI do Web Host. |
context |
Contexto inicial de página ou artefato para o host. |
Campos de env:
| Campo | Origem | Descrição |
|---|---|---|
APP_API_URL |
Variável de ambiente PUBLIC_API_URL |
URL base para todas as chamadas HTTP ao backend |
APP_AUTH_API_URL |
Igual a APP_API_URL |
URL do endpoint de autenticação (pode diferir em setups customizados) |
APP_WEBSOCKET_URL |
Derivada de APP_API_URL |
http:// → ws://, https:// → wss:// |
Escopos de theming:
| Escopo | Aplicado a |
|---|---|
global |
Tanto o chrome do host quanto todos os iframes filhos |
host |
Apenas o chrome do host. Também carrega i18n.app para o título, ícone e nome do app exibidos na barra lateral. |
children |
Apenas iframes filhos (injetado pelo script de proxy) |
Campos de hostConfig:
| Campo | Tipo | Padrão | Descrição |
|---|---|---|---|
session.type |
"non-persistent" | "cookie" |
"non-persistent" |
Modo de armazenamento do token |
history |
"hash" | "browser" |
"hash" |
Modo de history do Vue Router |
showAdmin |
boolean | true |
Exibe recursos de administração na UI |
allowSelectModel |
boolean | false |
Exibe o seletor de modelo de LLM |
startNavOpen |
boolean | false |
Expande a barra lateral de navegação ao carregar |
hideNavBar |
boolean | false |
Oculta completamente a barra lateral de navegação esquerda |
disableRightPanel |
boolean | false |
Desabilita o painel direito de artefatos |
hideSessionSelector |
boolean | false |
Oculta o seletor de sessões de chat |
additionalNavItems |
array | [] |
Itens extras injetados na barra lateral |
stateCache |
object | {} |
Configuração de cache LRU para o estado de iframes filhos |
allowAdditionalTags |
object | {} |
Whitelist de tags do sanitizador de HTML (Record<string, string[]>, tag → atributos permitidos) |
chat |
object | {} |
Sobrescritas da UI de chat (comportamento de colar-para-arquivo etc.) |
Fluxo de Autenticação
Se o usuário não estiver autenticado ao carregar a página, o wippy/facade redireciona para login_path antes de servir a página HTML. Após o login bem-sucedido, o usuário retorna à URL original. Nenhum estado de autenticação é passado pela própria configuração do Web Host — o Web Host confia no token de autenticação embutido em auth/env pela resposta autenticada da página.
Como o endpoint de configuração é servido pela mesma sessão autenticada que serviu a página HTML, APP_API_URL e a URL de WebSocket derivada refletem automaticamente o backend correto para aquele usuário.
A Função de Init do Módulo
A entrada de módulo JS registra window.initWippyApp na página. A página da facade a chama com o objeto de configuração buscado de /facade/config. O fe_mode seleciona qual módulo a facade carrega — module.js para compat, managed-layout.js para managed — e ambos expõem a mesma função de entrada initWippyApp. A escolha do módulo diz respeito a qual shell renderiza; é independente do estilo de embedding (página de módulo JS vs iframe manual).
initWippyApp(config, rootContainer?) retorna um emissor de eventos simples:
const events = window.initWippyApp(config, '#app')
events.on('ready', () => console.log('Wippy loaded'))
events.on('error', err => console.error('Failed to load:', err))
Quando chamada sem um container raiz, o host monta em um elemento padrão. O host assume a página e seu histórico de navegador a partir desse ponto.
Embedding manual em iframe (sem facade)
A página de módulo JS acima é o caminho padrão e recomendado, e o que a facade atual usa. Existe também um segundo mecanismo de embedding para casos em que você queira rodar o host completo dentro de um iframe — por exemplo, para ocupar apenas parte de uma página com isolamento mais forte da aplicação ao redor. Nesse modo você mesmo incorpora o host; a facade não produz essa página.
Você ainda pode reutilizar o endpoint /facade/config da facade para obter as URLs e a configuração: seu iframe_url (a entrada iframe.html do host, com ?waitForCustomConfig já anexado) e iframe_origin (o targetOrigin para PostMessage) existem exatamente para esse caminho. Você então cria o iframe por conta própria e completa o handshake de configuração.
Diferente do caminho de módulo JS, o host dentro do iframe solicita sua configuração: ele inicializa e posta uma mensagem get-config ao pai, e o pai responde com set-config. Assim, o pai escuta a requisição em vez de empurrar a configuração cegamente no load:
async function mountWippyIframe(auth) {
const response = await fetch('/api/public/facade/config')
if (!response.ok)
throw new Error(`Facade config request failed: ${response.status}`)
const cfg = await response.json()
const iframe = document.getElementById('wippy')
if (!(iframe instanceof HTMLIFrameElement))
throw new Error('Expected <iframe id="wippy">')
const iframeUrl = new URL(cfg.iframe_url)
if (iframeUrl.origin !== cfg.iframe_origin)
throw new Error('iframe_url and iframe_origin must identify the same origin')
const appConfig = {
$schema: `${cfg.facade_url}/schemas/wippy-context-2.2.json`,
auth,
env: cfg.env,
routePrefix: cfg.routePrefix,
themeMode: cfg.themeMode,
apiRoutes: cfg.apiRoutes,
axiosDefaults: cfg.axiosDefaults,
iconify: cfg.iconify,
importMap: cfg.importMap,
tanstack: cfg.tanstack,
theming: cfg.theming,
hostConfig: cfg.hostConfig,
context: { resourceId: '', resourceType: 'page' },
}
function onMessage(event) {
if (event.origin !== cfg.iframe_origin || event.source !== iframe.contentWindow)
return
let message
try {
message = typeof event.data === 'string' ? JSON.parse(event.data) : event.data
}
catch {
return
}
if (message?.type === '@gen2-chat' && message.action === 'get-config') {
event.source.postMessage(
JSON.stringify({ type: '@gen2-chat', action: 'set-config', ...appConfig }),
cfg.iframe_origin,
)
}
}
window.addEventListener('message', onMessage)
// iframe_url already includes ?waitForCustomConfig
iframe.src = iframeUrl.href
return function unmount() {
window.removeEventListener('message', onMessage)
iframe.remove()
}
}
A consulta ?waitForCustomConfig faz o bootstrap standalone esperar a mensagem set-config do pai antes de registrar o import map e importar módulos do app Host. O app ainda não foi montado. Sem a consulta, segue o caminho normal de configuração standalone.
O handshake usa o protocolo PostMessage @gen2-chat:
- O pai busca
GET /facade/config(ou fornece ele mesmo um payloadAppConfigequivalente) e cria o iframe apontando paraiframe_url. - O iframe em inicialização posta
{ type: '@gen2-chat', action: 'get-config' }ao pai. - O listener de
messagedo pai responde com{ type: '@gen2-chat', action: 'set-config', ...config }, direcionado aiframe_origin.
Para uma mudança posterior, envie outra mensagem set-config completa ao mesmo
iframe, usando iframe_origin. O Host aplica a alteração sem remontar. As
solicitações de API desse iframe usam a origem do documento iframe. Sirva a API
backend nessa mesma origem ou configure a política CORS do backend para permitir
a origem do documento. CORS é uma política do backend, não uma configuração de
AppConfig.
O Web Host extrai o payload AppConfig e prossegue com a inicialização completa. Para o protocolo completo de mensagens (envelope @gen2-chat e o enum IFrameMessageType), veja Proxy e Isolamento. Esse handshake de SetConfig é específico do embedding manual sem facade; o módulo wippy/facade carrega o Web Host como um módulo JS.
Configurando o Módulo da Facade
Os parâmetros de wippy/facade que produzem a resposta de configuração acima são definidos no seu _index.yaml. Um exemplo real do app-template:
- name: facade
kind: ns.dependency
component: wippy/facade
version: '>=v0.5.37'
parameters:
- name: server
value: app:gateway
- name: router
value: app:api.public
- name: app_title
value: Wippy App
- name: app_name
value: Wippy App
- name: app_icon
value: "wippy:logo"
- name: show_admin
value: "false"
- name: hide_nav_bar
value: "true"
- name: login_path
value: /app/login.html
- name: session_type
value: non-persistent
- name: history_mode
value: browser
- name: custom_css
value: "@import url('https://fonts.googleapis.com/css2?family=Poppins...');
body { font-family: 'Poppins', sans-serif; }"
- name: css_variables
value: '{"--p-primary":"#6366f1"}'
- name: host_custom_css
value: ".wippy-host-app .chat-container { background: var(--p-content-background); }"
- name: tanstack
value: '{"lists":{"refetchOnWindowFocus":true}}'
Para a lista completa de parâmetros disponíveis e seus padrões, veja a referência do módulo Facade.
Encaminhar o mapa de importação ao Host
O receptor aceita SetConfig do iframe somente da janela física do pai. Quando consegue acessar a origem do pai, também compara event.origin com ela. Em um Web Fragment, a mensagem deve apontar para o fragmentId próprio daquela ocorrência.
O nome do requisito do facade é import_map; /facade/config o devolve como cfg.importMap. O shell passa esse valor ao bootstrap compartilhado do import map antes de importar o módulo do Host. Depois, passa importMap: cfg.importMap na configuração inicial de initWippyApp. Consulte a sequência de inicialização. Uma alteração no import map não afeta um documento existente. Recarregue-o ou crie-o novamente para usar o novo mapa.
Consulte Módulo Facade e Sequência de bootstrap para ver as versões mínimas das integrações diretas com Host e com Facade.
UI policy in 1.0.62
O Web Host 1.0.62 compartilha a seguinte configuração de chat com aplicativos filhos. hostConfig pertence ao contêiner Web Host e nunca é incluído na configuração dos filhos. Um layout gerenciado não inclui chat integrado por padrão. Aplicativos executados inteiramente como filhos usam campos de nível superior ou substituições do pacote.