Facade Entry Point
This page is an integration reference. The shell bootstrap and manual iframe blocks isolate specific contracts; they are not substitutes for a complete login flow or application project.
The wippy/facade backend module delivers the Web Host to users. It serves the
HTML shell and /facade/config. The shell loads the Web Host module, checks the
browser's stored authentication token, redirects unauthenticated users, and
assembles deployment-specific configuration for the CDN-hosted frontend bundle.
The bundle itself contains no deployment-specific configuration.
The HTML Page
When a user navigates to a Wippy application, the Web Host module takes over the page and its browser history, so the host runs as the application rather than inside an iframe.
The facade loads one of two JS-module entries depending on the configured fe_mode:
module.js— the compat shell (default): the standard nav-sidebar + page-area + chat-right-panel layout.managed-layout.js— the managed shell (opt-in, early access): the declarative multi-panel layout.
A simplified version of the bootstrap call looks like this. The shipped shell also loads configured extra scripts, installs the Web Host import map, handles errors, and applies the persisted theme before this call:
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 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')
await import(cfg.facade_url + cfg.module_file)
const appConfig = {
$schema: `${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,
axiosDefaults: cfg.axiosDefaults,
theming: cfg.theming,
hostConfig: cfg.hostConfig,
context: { resourceId: '', resourceType: 'page' },
}
window.initWippyApp(appConfig, '#app')
Fetch path.
/facade/configis the path the facade registers on the public router. The requested URL also includes that router's prefix. With the example prefix/api/public, request/api/public/facade/config, as the shipped facade page and bootstrap example do. Contract descriptions below use the registry-local path.
The Config Flow
The config flow has four steps:
- The page's inline JavaScript calls
GET /facade/configon the same origin as the page. This endpoint is registered bywippy/facadeon the public router. - The shell reads
@wippy_token_infofrom localStorage. If the value is missing or cannot be decoded, the browser redirects tologin_path. - The shell loads
extraScripts, installs the Web Host import map, and imports the module selected bymodule_file. - The shell adds
$schema,auth, andcontextto the supported deployment fields, then callswindow.initWippyApp(appConfig, rootContainer?).
The Web Host receives that assembled AppConfig and proceeds with full initialization. From this point forward the page script is passive — all user interaction happens inside the mounted host.
The CDN-hosted bundle is identical across deployments; deployment-specific URLs and branding arrive in the config response, while the bearer token comes from browser storage.
Config response vs
AppConfig./facade/configdoes not return a completeAppConfig: it has no$schema,auth, orcontext. Fields such asfacade_url,iframe_origin,iframe_url, andlogin_pathare shell settings, whileenv,theming, andhostConfigare inputs to the assembledAppConfig.
The /facade/config Response
The config endpoint returns shell settings and Web Host configuration assembled
by wippy/facade from module parameters and the running environment. This is an
example configured response; optional JSON blocks that remain empty are omitted:
{
"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 } },
"extraScripts": ["/monitoring.js"],
"theming": {
"global": {
"customCSS": "@import url('https://fonts.googleapis.com/...');",
"cssVariables": { "--p-primary": "#6366f1" },
"iconSets": {}
},
"host": {
"customCSS": ".wippy-host-app .chat-container { background: var(--p-content-background); }",
"cssVariables": {},
"iconSets": {},
"i18n": {
"app": {
"title": "My App",
"icon": "wippy:logo",
"appName": "My Application"
}
}
},
"children": {
"customCSS": "",
"cssVariables": {}
}
},
"hostConfig": {
"session": { "type": "non-persistent" },
"history": "hash",
"renderEngine": "iframe",
"showAdmin": true,
"allowSelectModel": false,
"startNavOpen": false,
"hideNavBar": false,
"disableRightPanel": false,
"hideSessionSelector": false,
"additionalNavItems": [
{ "id": "reports", "name": "Reports", "title": "Reports", "icon": "tabler:report", "order": 10 }
],
"stateCache": { "maxPages": 50, "maxSizePerPage": 1048576 },
"allowAdditionalTags": { "w-chart": ["data", "type"] },
"chat": { "convertPasteToFile": { "enabled": true, "minFileSize": 1024, "allowHtml": false } }
}
}
Field Reference
Shell and integration fields — used by the standard shell or a custom embedder:
| Field | Description |
|---|---|
facade_url |
Base CDN URL for the Web Host bundle. Used to resolve the module entry and vendor scripts. |
iframe_origin |
Origin header value of the CDN. Used as the targetOrigin for PostMessage in manual iframe embeddings (see below). |
iframe_url |
Full iframe src including ?waitForCustomConfig. Used only by manual, facade-less iframe embeddings (see below). |
login_path |
Path on the page's origin to redirect unauthenticated users to. |
login_redirect_param |
Optional query parameter that receives the requested relative URL during the client-side login redirect. |
mode |
Normalized frontend mode: compat or managed. |
module_file |
Module selected by mode: /module.js or /managed-layout.js. |
themePersist |
Configured theme persistence mode, also available to external pages. |
themeStorageKey |
Configured cookie or localStorage key, also available to external pages. |
extraScripts |
Optional scripts the shell loads before the Web Host module. |
Web Host fields returned by the endpoint — copied selectively into the
AppConfig assembled by the page:
| Field | Description |
|---|---|
env |
Runtime URLs injected as top-level AppConfig.env. |
routePrefix |
API URL prefix forwarded to child apps. |
themeMode |
Initial theme mode: auto, light, or dark. A persisted choice takes precedence in the standard shell. |
axiosDefaults |
Axios instance defaults forwarded to child apps. |
apiRoutes |
Override individual API endpoint paths (top-level AppConfig field). |
tanstack |
TanStack Query defaults returned by the endpoint. See the forwarding limitation below. |
theming |
CSS customization split into three scopes. |
hostConfig |
Web Host feature flags and UI configuration. |
The standard shell itself adds these required AppConfig fields:
| Field | Source |
|---|---|
$schema |
<facade_url>/schemas/wippy-context-2.0.xsd |
auth |
Token read from @wippy_token_info; the current shell synthesizes an expiry one day from initialization. |
context |
{ resourceId: '', resourceType: 'page' } |
Current
tanstackforwarding limitation. The config handler returns a configuredtanstackobject, and Web Host acceptsAppConfig.tanstack. The standard facade shell does not currently copycfg.tanstackinto itsinitWippyAppargument, so the facade parameter has no effect on that path. A manual embedder may includetanstack: cfg.tanstackin its assembledAppConfig.
env fields:
| Field | Source | Description |
|---|---|---|
APP_API_URL |
PUBLIC_API_URL env var |
Base URL for all backend HTTP calls |
APP_AUTH_API_URL |
Same as APP_API_URL |
Auth endpoint URL (may differ in custom setups) |
APP_WEBSOCKET_URL |
Derived from APP_API_URL |
http:// → ws://, https:// → wss:// |
theming scopes:
| Scope | Applied to |
|---|---|
global |
Both the host chrome and all child page render contexts |
host |
Host chrome only. Also carries i18n.app for the app title, icon, and name shown in the sidebar. |
children |
Child page render contexts (srcdoc iframes or Web Fragments) |
hostConfig fields:
| Field | Type | Default | Description |
|---|---|---|---|
session.type |
"non-persistent" | "cookie" |
"non-persistent" |
Token storage mode |
history |
"hash" | "browser" |
"hash" |
Vue Router history mode |
renderEngine |
"iframe" | "fragment" |
"iframe" |
Render engine for packaged view.page applications |
showAdmin |
boolean | true |
Show admin features in UI |
allowSelectModel |
boolean | false |
Show LLM model picker |
startNavOpen |
boolean | false |
Expand nav sidebar on load |
hideNavBar |
boolean | false |
Hide left navigation sidebar entirely |
disableRightPanel |
boolean | false |
Disable right artifact panel |
hideSessionSelector |
boolean | false |
Hide the chat session picker |
additionalNavItems |
array | [] |
Extra items injected into the sidebar |
stateCache |
object | {} |
LRU cache config for child page state |
allowAdditionalTags |
object | {} |
HTML sanitizer tag whitelist (Record<string, string[]>, tag → allowed attributes) |
chat |
object | {} |
Chat UI overrides (paste-to-file behavior, etc.) |
Authentication Flow
The facade serves the HTML shell and public config response before it knows the
client-held bearer token. In the browser, the shell reads
@wippy_token_info from localStorage. A missing value or invalid JSON triggers a
redirect to login_path. If login_redirect_param is configured, the shell
adds the current path, query, and hash so the login flow can return the user to
the requested URL.
For a valid stored value, the shell copies its token into AppConfig.auth and
synthesizes expiresAt as one day after initialization. The config endpoint
itself contains neither the token nor user-specific auth state. APP_API_URL
and APP_WEBSOCKET_URL are deployment settings, not per-user values.
The Module Init Function
Both JS-module entries register the same window.initWippyApp function. The
choice of module determines which shell renders and is independent of the
embedding style (JS-module page vs manual iframe).
initWippyApp(appConfig, rootContainer?) returns a simple event emitter:
const events = window.initWippyApp(appConfig, '#app')
events.on('ready', () => console.log('Wippy loaded'))
events.on('error', err => console.error('Failed to load:', err))
When called without a root container, the host mounts into a default element.
Manual (facade-less) iframe embedding
The JS-module page above is the standard, recommended path and the one the current facade uses. There is also a second embedding mechanism for cases where you want to run the full host inside an iframe — for example to occupy only part of a page with stronger isolation from the surrounding application. In this mode you embed the host yourself; the facade does not produce this page.
You can still reuse the facade's /facade/config endpoint to obtain deployment
settings. Its iframe_url (the host's iframe.html entry with
?waitForCustomConfig appended) and iframe_origin (the PostMessage
targetOrigin) support this path. The parent must obtain auth through its own
client flow and assemble a complete AppConfig before answering the handshake.
Unlike the JS-module path, the host inside the iframe requests its config: it boots and posts a get-config message to the parent, and the parent replies with set-config. Given an <iframe id="wippy"></iframe> in the parent document, listen for the request rather than pushing config blindly on 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.0.xsd`,
auth,
env: cfg.env,
routePrefix: cfg.routePrefix,
themeMode: cfg.themeMode,
apiRoutes: cfg.apiRoutes,
axiosDefaults: cfg.axiosDefaults,
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()
}
}
Call mountWippyIframe with an auth object containing the current bearer
token and an ISO 8601 expiresAt. Do not source that token from
/facade/config; the endpoint does not return one. Retain the returned
unmount function and call it when the embedding surface is removed so the
window listener and iframe do not survive their owner.
The parent-side checks above protect the parent from accepting messages from a
different frame. At Web Host 1.0.56, the iframe's inbound SetConfig handler
checks only the envelope type and action; it does not authenticate
event.origin or event.source, and a later matching message can replace the
configuration. Treat every script or window that can message the iframe as part
of the trusted configuration boundary. Iframe DOM and style isolation is not
configuration-authority isolation.
The ?waitForCustomConfig query parameter (already present in iframe_url) is the key signal. It tells the Web Host to pause initialization — the app mounts but deliberately does not attempt to resolve authentication or load routes until it receives a set-config message. Without it the Web Host would try to read auth tokens from URL parameters or defaults, which is not appropriate for embedded deployments.
The handshake uses the @gen2-chat PostMessage protocol:
- The parent fetches
GET /facade/config(or supplies equivalent deployment settings), assembles a completeAppConfig, and creates the iframe pointing atiframe_url. - The booting iframe posts
{ type: '@gen2-chat', action: 'get-config' }to the parent. - The parent's
messagelistener responds with{ type: '@gen2-chat', action: 'set-config', ...appConfig }, targeted atiframe_origin.
The Web Host extracts the AppConfig payload and proceeds with full initialization. For the full message protocol (@gen2-chat envelope and the IFrameMessageType enum), see Proxy & Isolation. This SetConfig handshake is specific to manual, facade-less embedding; the wippy/facade module loads the Web Host as a JS module instead.
Configuring the Facade Module
Set the wippy/facade parameters that produce the config response in
_index.yaml. This example comes from app-template:
- name: facade
kind: ns.dependency
component: wippy/facade
version: '0.6.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); }"
For the full list of available parameters and their defaults, see the Facade module reference.