Punto de Entrada del Facade
El módulo de backend wippy/facade es el punto de entrada que entrega el Web Host a los usuarios. Sirve una página HTML que carga el módulo JS del Web Host, gestiona las redirecciones de autenticación, expone un endpoint /facade/config y traslada la configuración específica del despliegue al bundle de frontend alojado en el CDN. No hay ninguna configuración integrada en el propio bundle: cada despliegue aporta la suya mediante este mecanismo.
La página HTML
Cuando un usuario navega a una aplicación de Wippy, wippy/facade sirve una página HTML. Esta página es ligera: carga un módulo JS del Web Host desde el CDN e inicializa el host con la configuración devuelta por /facade/config. El módulo se hace cargo de toda la página — incluido su historial de navegador — de modo que el host se ejecuta como la aplicación completa y no dentro de un iframe.
El facade carga uno de dos puntos de entrada de módulo JS según el fe_mode configurado:
module.js— el shell compat (por defecto): el layout estándar de barra lateral de navegación + área de página + panel derecho de chat.managed-layout.js— el shell managed (opt-in, acceso anticipado): el layout declarativo multipanel.
Una versión simplificada de la página tiene este aspecto:
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')
La página obtiene su configuración y se la entrega a la función de inicialización del módulo. El host se monta en la página, se hace cargo del enrutamiento y del historial del navegador, y continúa con la inicialización completa.
Nota sobre la ruta del fetch.
/facade/configes la ruta que el facade registra en el router público; la URL real que solicita su página incluye el prefijo de ese router. Con el prefijo de ejemplo/api/public, es/api/public/facade/config, exactamente lo que solicita la página del facade entregada. Los fragmentos inlinefetch('/facade/config')aquí están abreviados por legibilidad.
El flujo de configuración
El flujo de configuración tiene dos pasos:
-
La página obtiene
/facade/configdel router público de su mismo origen. -
El shell obtiene el mapa de importación del Host, carga el bootstrap compartido y registra el mapa del Host combinado con
cfg.importMap. Solo entonces importa dinámicamentecfg.module_file. El shell construyeAppConfigcon los campos recibidos y añade el esquemawippy-context-2.2,authycontextantes de llamar ainitWippyApp.
El Web Host extrae el payload de AppConfig del objeto de configuración y continúa con la inicialización completa. A partir de este punto, el script de la página es pasivo: toda la interacción del usuario ocurre dentro del host montado.
Este patrón significa que el bundle alojado en el CDN nunca contiene URLs, tokens ni branding específicos del despliegue. El bundle es idéntico para todos los despliegues. Solo difiere el payload de configuración.
El endpoint devuelve ajustes del shell y campos seleccionados del Web Host. No devuelve un AppConfig completo: el shell añade $schema, auth y context antes de llamar a initWippyApp. Facade 0.6.46 proporciona schema_url: wippy-context-2.0.xsd para la política heredada y wippy-context-2.1.json para la política moderna. Si una versión anterior de Facade no incluye el campo, el shell usa el respaldo XSD 2.0. Web Host 1.0.66 acepta ambos esquemas versionados y los migra al esquema canónico 2.2.
La respuesta de /facade/config
El endpoint devuelve ajustes del shell y campos seleccionados del Web Host. No devuelve un AppConfig completo: el shell añade $schema, auth y context antes de llamar a initWippyApp. Facade 0.6.46 proporciona schema_url: wippy-context-2.0.xsd para la política heredada y wippy-context-2.1.json para la política moderna. Si una versión anterior de Facade no incluye el campo, el shell usa el respaldo XSD 2.0. Web Host 1.0.66 acepta ambos esquemas versionados y los migra al esquema 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": {}
}
Referencia de campos
Campos de nivel de shell: los consume la página embebedora para construirse a sí misma; no forman parte del AppConfig del hijo:
| Campo | Descripción |
|---|---|
facade_url |
URL base del CDN para el bundle del Web Host. Se usa para resolver el punto de entrada del módulo y los scripts de vendor. |
iframe_origin |
Valor de la cabecera Origin del CDN. Se usa como targetOrigin para PostMessage en embebidos manuales en iframe (vea abajo). |
iframe_url |
src completo del iframe, incluido ?waitForCustomConfig. Lo usan únicamente los embebidos manuales en iframe, sin facade (vea abajo). |
login_path |
Ruta en el origen de la página a la que redirigir a los usuarios no autenticados. |
Campos del AppConfig del hijo: se pasan a la función de inicialización del host y los consume el host en ejecución:
| Campo | Descripción |
|---|---|
$schema |
cfg.schema_url; si no está presente: <facade_url>/schemas/wippy-context-2.0.xsd |
auth |
Bearer token de runtime y su caducidad, inyectados como AppConfig.auth. |
env |
URLs de runtime inyectadas como AppConfig.env de nivel superior. |
routePrefix |
Prefijo de URL de la API reenviado a las aplicaciones hijas. |
axiosDefaults |
Valores por defecto de la instancia de Axios reenviados a las aplicaciones hijas. |
apiRoutes |
Anula rutas individuales de endpoints de la API (campo de nivel superior de AppConfig). |
tanstack |
Valores por defecto de TanStack Query: globales + por categoría basada en rol (content/lists); campo de nivel superior de AppConfig. El valor por defecto del host es refetchOnWindowFocus:false. |
iconify |
Fuentes de Iconify configuradas explícitamente y reenviadas desde cfg.iconify. |
importMap |
Extensión opcional del import map del navegador desde cfg.importMap; el shell la registra antes de importar módulos del Host. |
theming |
Personalización de CSS dividida en tres ámbitos. |
hostConfig |
Feature flags y configuración de UI del Web Host. |
context |
Contexto inicial de página o artefacto para el host. |
Campos de env:
| Campo | Origen | Descripción |
|---|---|---|
APP_API_URL |
Variable de entorno PUBLIC_API_URL |
URL base para todas las llamadas HTTP al backend |
APP_AUTH_API_URL |
Igual que APP_API_URL |
URL del endpoint de autenticación (puede diferir en configuraciones a medida) |
APP_WEBSOCKET_URL |
Derivada de APP_API_URL |
http:// → ws://, https:// → wss:// |
Ámbitos de theming:
| Ámbito | Se aplica a |
|---|---|
global |
Tanto el chrome del host como todos los iframes hijos |
host |
Solo el chrome del host. También lleva i18n.app para el título, el icono y el nombre de la aplicación mostrados en la barra lateral. |
children |
Solo los iframes hijos (inyectado por el script del proxy) |
Campos de hostConfig:
| Campo | Tipo | Por defecto | Descripción |
|---|---|---|---|
session.type |
"non-persistent" | "cookie" |
"non-persistent" |
Modo de almacenamiento del token |
history |
"hash" | "browser" |
"hash" |
Modo de historial de Vue Router |
showAdmin |
boolean | true |
Muestra las funciones de administración en la UI |
allowSelectModel |
boolean | false |
Muestra el selector de modelo LLM |
startNavOpen |
boolean | false |
Expande la barra lateral de navegación al cargar |
hideNavBar |
boolean | false |
Oculta por completo la barra lateral de navegación izquierda |
disableRightPanel |
boolean | false |
Deshabilita el panel derecho de artefactos |
hideSessionSelector |
boolean | false |
Oculta el selector de sesiones de chat |
additionalNavItems |
array | [] |
Elementos adicionales inyectados en la barra lateral |
stateCache |
object | {} |
Configuración de caché LRU para el estado de los iframes hijos |
allowAdditionalTags |
object | {} |
Lista blanca de etiquetas del sanitizador HTML (Record<string, string[]>, etiqueta → atributos permitidos) |
chat |
object | {} |
Anulaciones de la UI de chat (comportamiento de pegar como archivo, etc.) |
Flujo de autenticación
Si el usuario no está autenticado cuando carga la página, wippy/facade redirige a login_path antes de servir la página HTML. Tras un inicio de sesión correcto, se devuelve al usuario a la URL original. No se pasa ningún estado de autenticación a través de la propia configuración del Web Host: el Web Host confía en el token de autenticación embebido en auth/env por la respuesta de la página autenticada.
Como el endpoint de configuración lo sirve la misma sesión autenticada que sirvió la página HTML, APP_API_URL y la URL de WebSocket derivada reflejan automáticamente el backend correcto para ese usuario.
La función de inicialización del módulo
El punto de entrada del módulo JS registra window.initWippyApp en la página. La página del facade lo llama con el objeto de configuración obtenido de /facade/config. fe_mode selecciona qué módulo carga el facade — module.js para compat, managed-layout.js para managed — y ambos exponen la misma función de entrada initWippyApp. La elección del módulo determina qué shell se renderiza; es independiente del estilo de embebido (página con módulo JS frente a iframe manual).
initWippyApp(config, rootContainer?) devuelve un emisor de eventos sencillo:
const events = window.initWippyApp(config, '#app')
events.on('ready', () => console.log('Wippy loaded'))
events.on('error', err => console.error('Failed to load:', err))
Cuando se llama sin contenedor raíz, el host se monta en un elemento por defecto. A partir de ese momento, el host se hace cargo de la página y de su historial de navegador.
Embebido manual en iframe (sin facade)
La página con módulo JS anterior es la vía estándar y recomendada, y la que usa el facade actual. Existe además un segundo mecanismo de embebido para los casos en que quiera ejecutar el host completo dentro de un iframe, por ejemplo para ocupar solo una parte de una página con un aislamiento más fuerte respecto de la aplicación circundante. En este modo, usted embebe el host por su cuenta; el facade no produce esta página.
Aun así puede reutilizar el endpoint /facade/config del facade para obtener las URLs y la configuración: su iframe_url (el punto de entrada iframe.html del host, con ?waitForCustomConfig ya añadido) e iframe_origin (el targetOrigin para PostMessage) existen exactamente para esta vía. Después crea usted mismo el iframe y completa el handshake de configuración.
A diferencia de la vía del módulo JS, el host dentro del iframe solicita su configuración: arranca y envía un mensaje get-config al padre, y el padre responde con set-config. Así que el padre escucha la petición en lugar de enviar la configuración a ciegas en el evento 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()
}
}
La consulta ?waitForCustomConfig hace que el bootstrap standalone espere el mensaje set-config del padre antes de registrar el import map e importar los módulos de la aplicación Host. La aplicación aún no se ha montado. Sin la consulta, se ejecuta la ruta normal de configuración standalone.
El handshake usa el protocolo PostMessage @gen2-chat:
- El padre solicita
GET /facade/config(o suministra él mismo un payloadAppConfigequivalente) y crea el iframe apuntando aiframe_url. - El iframe en arranque envía
{ type: '@gen2-chat', action: 'get-config' }al padre. - El listener
messagedel padre responde con{ type: '@gen2-chat', action: 'set-config', ...config }, dirigido aiframe_origin.
Para un cambio posterior, envíe otro mensaje set-config completo al mismo
iframe y a iframe_origin. Host aplica el cambio sin volver a montar la app. Las
solicitudes API de este iframe usan el origen del documento iframe. Sirva la API
backend desde ese mismo origen o configure su política CORS para permitir el
origen del documento iframe. CORS es una política del backend, no un ajuste de
AppConfig.
El Web Host extrae el payload de AppConfig y continúa con la inicialización completa. Para el protocolo de mensajes completo (el sobre @gen2-chat y el enum IFrameMessageType), vea Proxy y Aislamiento. Este handshake SetConfig es específico del embebido manual sin facade; el módulo wippy/facade carga en su lugar el Web Host como módulo JS.
Configurar el módulo del facade
Los parámetros de wippy/facade que producen la respuesta de configuración anterior se establecen en su _index.yaml. Un ejemplo real de 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 la lista completa de parámetros disponibles y sus valores por defecto, vea la referencia del módulo Facade.
Pasar el mapa de importación al Host
El receptor solo acepta SetConfig del iframe desde la ventana física del padre. Cuando puede acceder al origen del padre, también compara event.origin con ese origen. En un Web Fragment, el mensaje debe dirigirse al fragmentId propio de esa ocurrencia.
El requisito del facade se llama import_map; /facade/config lo devuelve como cfg.importMap. El shell entrega ese valor al bootstrap compartido del import map antes de importar el módulo del Host. Después pasa importMap: cfg.importMap en la configuración inicial de initWippyApp. Consulta la secuencia de bootstrap. Un cambio del import map no afecta a un documento existente. Recárguelo o vuelva a crearlo para usar el nuevo mapa.
Consulte Módulo Facade y Secuencia de bootstrap para conocer las versiones mínimas de las integraciones directas con Host y con Facade.
UI policy in 1.0.62
Web Host 1.0.62 comparte la siguiente configuración de chat con las aplicaciones hijas. hostConfig pertenece al contenedor Web Host y nunca se incluye en los datos de los hijos. Un diseño administrado no incluye un chat integrado de forma predeterminada. Las aplicaciones que funcionan completamente como hijos usan campos de nivel superior o sustituciones del paquete.