Точка входа фасада
Бэкенд-модуль wippy/facade — это точка входа, доставляющая Web Host пользователям. Он раздаёт HTML-страницу, которая загружает JS-модуль Web Host, обрабатывает редиректы аутентификации, предоставляет конечную точку /facade/config и переносит конфигурацию конкретного развёртывания во фронтенд-бандл, размещённый на CDN. В сам бандл никакая конфигурация не запекается — каждое развёртывание предоставляет свою через этот механизм.
HTML-страница
Когда пользователь переходит в приложение Wippy, wippy/facade раздаёт HTML-страницу. Эта страница тонкая: она загружает JS-модуль Web Host с CDN и инициализирует хост конфигурацией, возвращённой из /facade/config. Модуль забирает себе всю страницу, включая историю браузера, поэтому хост работает как всё приложение, а не внутри iframe.
Фасад загружает одну из двух точек входа JS-модуля в зависимости от настроенного fe_mode:
module.js— оболочка compat (по умолчанию): стандартная вёрстка с навигационной боковой панелью, областью страницы и правой панелью чата.managed-layout.js— оболочка managed (по согласию, ранний доступ): декларативная многопанельная вёрстка.
Упрощённая версия страницы выглядит так:
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')
Страница получает свою конфигурацию и передаёт её функции инициализации модуля. Хост монтируется в страницу, забирает маршрутизацию и историю браузера и переходит к полной инициализации.
Замечание о пути fetch.
/facade/config— это путь, который фасад регистрирует на публичном роутере; фактический URL, который запрашивает ваша страница, включает префикс этого роутера. С примером префикса/api/publicэто будет/api/public/facade/config— ровно то, что запрашивает поставляемая страница фасада. Встроенные фрагментыfetch('/facade/config')здесь сокращены для читаемости.
Поток конфигурации
Поток конфигурации состоит из двух шагов:
-
Страница запрашивает
/facade/configу публичного роутера того же origin. -
Оболочка получает import map Host, загружает общий bootstrap и регистрирует карту Host, объединённую с
cfg.importMap. Только после этого она динамически импортируетcfg.module_file. Оболочка собираетAppConfigиз возвращённых полей и добавляет схемуwippy-context-2.2,authиcontextперед вызовомinitWippyApp.
Web Host извлекает полезную нагрузку AppConfig из объекта конфигурации и переходит к полной инициализации. С этого момента скрипт страницы пассивен — всё взаимодействие с пользователем происходит внутри смонтированного хоста.
Такой подход означает, что размещённый на CDN бандл никогда не содержит URL, токенов или брендинга конкретного развёртывания. Бандл одинаков для всех развёртываний. Различается только полезная нагрузка конфигурации.
Эндпоинт возвращает настройки оболочки и выбранные поля Web Host. Он не возвращает полный AppConfig: оболочка добавляет $schema, auth и context перед вызовом initWippyApp. Facade 0.6.46 передаёт schema_url: wippy-context-2.0.xsd для старой политики и wippy-context-2.1.json для современной политики. Если более старая версия Facade не передаёт это поле, оболочка использует резервную схему XSD 2.0. Web Host 1.0.66 принимает обе версии схемы и преобразует их в канонический формат 2.2.
Ответ /facade/config
Эндпоинт возвращает настройки оболочки и выбранные поля Web Host. Он не возвращает полный AppConfig: оболочка добавляет $schema, auth и context перед вызовом initWippyApp. Facade 0.6.46 передаёт schema_url: wippy-context-2.0.xsd для старой политики и wippy-context-2.1.json для современной политики. Если более старая версия Facade не передаёт это поле, оболочка использует резервную схему XSD 2.0. Web Host 1.0.66 принимает обе версии схемы и преобразует их в канонический формат 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": {}
}
Справочник полей
Поля уровня оболочки — потребляются встраивающей страницей для собственного построения; в дочерний AppConfig не входят:
| Поле | Описание |
|---|---|
facade_url |
Базовый URL CDN для бандла Web Host. Используется для разрешения входа модуля и вендорных скриптов. |
iframe_origin |
Значение заголовка Origin для CDN. Используется как targetOrigin для PostMessage при ручном встраивании в iframe (см. ниже). |
iframe_url |
Полный src iframe, включая ?waitForCustomConfig. Используется только при ручном встраивании в iframe без фасада (см. ниже). |
login_path |
Путь на origin страницы, куда перенаправляются неаутентифицированные пользователи. |
Поля дочернего AppConfig — передаются функции инициализации хоста и потребляются работающим хостом:
| Поле | Описание |
|---|---|
$schema |
cfg.schema_url; если поле отсутствует: <facade_url>/schemas/wippy-context-2.0.xsd |
auth |
Рантайм-токен bearer и срок его действия, внедряемые как AppConfig.auth. |
env |
Рантайм-URL, внедряемые как AppConfig.env верхнего уровня. |
routePrefix |
Префикс URL API, передаваемый дочерним приложениям. |
axiosDefaults |
Умолчания экземпляра axios, передаваемые дочерним приложениям. |
apiRoutes |
Переопределение путей отдельных конечных точек API (поле AppConfig верхнего уровня). |
tanstack |
Умолчания TanStack Query — глобальные + по ролевой категории (content/lists); поле AppConfig верхнего уровня. Умолчание хоста — refetchOnWindowFocus:false. |
iconify |
Явно настроенные источники Iconify, переданные из cfg.iconify. |
importMap |
Необязательное расширение browser import map из cfg.importMap; оболочка регистрирует его до импорта модулей Host. |
theming |
Настройка CSS, разделённая на три области. |
hostConfig |
Флаги функциональности и настройка интерфейса Web Host. |
context |
Начальный контекст страницы или артефакта для хоста. |
Поля env:
| Поле | Источник | Описание |
|---|---|---|
APP_API_URL |
Переменная окружения PUBLIC_API_URL |
Базовый URL для всех HTTP-вызовов к бэкенду |
APP_AUTH_API_URL |
То же, что APP_API_URL |
URL конечной точки аутентификации (может отличаться в нестандартных конфигурациях) |
APP_WEBSOCKET_URL |
Выводится из APP_API_URL |
http:// → ws://, https:// → wss:// |
Области theming:
| Область | Применяется к |
|---|---|
global |
И к оболочке хоста, и ко всем дочерним iframe |
host |
Только к оболочке хоста. Также несёт i18n.app для заголовка приложения, иконки и имени, отображаемых в боковой панели. |
children |
Только к дочерним iframe (вставляется скриптом прокси) |
Поля hostConfig:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
session.type |
"non-persistent" | "cookie" |
"non-persistent" |
Режим хранения токена |
history |
"hash" | "browser" |
"hash" |
Режим истории Vue Router |
showAdmin |
boolean | true |
Показывать административные возможности в интерфейсе |
allowSelectModel |
boolean | false |
Показывать выбор модели LLM |
startNavOpen |
boolean | false |
Разворачивать навигационную боковую панель при загрузке |
hideNavBar |
boolean | false |
Полностью скрыть левую навигационную боковую панель |
disableRightPanel |
boolean | false |
Отключить правую панель артефактов |
hideSessionSelector |
boolean | false |
Скрыть выбор сессии чата |
additionalNavItems |
array | [] |
Дополнительные пункты, добавляемые в боковую панель |
stateCache |
object | {} |
Конфигурация LRU-кэша состояния дочерних iframe |
allowAdditionalTags |
object | {} |
Белый список тегов санитайзера HTML (Record<string, string[]>, тег → допустимые атрибуты) |
chat |
object | {} |
Переопределения интерфейса чата (поведение вставки в файл и т. д.) |
Поток аутентификации
Если пользователь не аутентифицирован при загрузке страницы, wippy/facade перенаправляет на login_path до раздачи HTML-страницы. После успешного входа пользователь возвращается на исходный URL. Состояние аутентификации не передаётся через саму конфигурацию Web Host — Web Host доверяет токену аутентификации, встроенному в auth/env ответом аутентифицированной страницы.
Поскольку конечную точку конфигурации обслуживает та же аутентифицированная сессия, что раздала HTML-страницу, APP_API_URL и производный URL WebSocket автоматически отражают правильный бэкенд для этого пользователя.
Функция инициализации модуля
Точка входа JS-модуля регистрирует на странице window.initWippyApp. Страница фасада вызывает её с объектом конфигурации, полученным из /facade/config. fe_mode выбирает, какой модуль загружает фасад — module.js для compat, managed-layout.js для managed — и оба предоставляют одну и ту же входную функцию initWippyApp. Выбор модуля определяет, какая оболочка отрисовывается; он не зависит от стиля встраивания (страница с JS-модулем против ручного iframe).
initWippyApp(config, rootContainer?) возвращает простой генератор событий:
const events = window.initWippyApp(config, '#app')
events.on('ready', () => console.log('Wippy loaded'))
events.on('error', err => console.error('Failed to load:', err))
При вызове без корневого контейнера хост монтируется в элемент по умолчанию. С этого момента хост забирает себе страницу и её историю браузера.
Ручное встраивание в iframe (без фасада)
Описанная выше страница с JS-модулем — стандартный, рекомендуемый путь, и именно его использует текущий фасад. Существует и второй механизм встраивания для случаев, когда полноценный хост нужно запустить внутри iframe — например, чтобы занять лишь часть страницы с более сильной изоляцией от окружающего приложения. В этом режиме вы встраиваете хост сами; фасад такую страницу не создаёт.
Вы всё равно можете переиспользовать конечную точку фасада /facade/config, чтобы получить URL и конфигурацию: её iframe_url (точка входа хоста iframe.html с уже добавленным ?waitForCustomConfig) и iframe_origin (targetOrigin для PostMessage) существуют именно для этого пути. Далее вы сами создаёте iframe и завершаете рукопожатие конфигурации.
В отличие от пути с JS-модулем, хост внутри iframe запрашивает свою конфигурацию: он загружается и отправляет родителю сообщение get-config, а родитель отвечает set-config. Поэтому родитель слушает запрос, а не проталкивает конфигурацию вслепую по событию 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()
}
}
Параметр ?waitForCustomConfig заставляет standalone bootstrap ждать сообщение set-config от родителя до регистрации import map и импорта модулей приложения Host. На этом этапе приложение еще не смонтировано. Без параметра выполняется обычный standalone-путь конфигурации.
Рукопожатие использует протокол PostMessage @gen2-chat:
- Родитель запрашивает
GET /facade/config(или сам предоставляет эквивалентную полезную нагрузкуAppConfig) и создаёт iframe, указывающий наiframe_url. - Загружающийся iframe отправляет родителю
{ type: '@gen2-chat', action: 'get-config' }. - Слушатель
messageродителя отвечает{ type: '@gen2-chat', action: 'set-config', ...config }, нацеленным наiframe_origin.
При последующем изменении конфигурации отправьте полное сообщение set-config
этому же iframe, указав iframe_origin. Host применяет изменение без
перемонтирования. API-запросы iframe используют origin его документа.
Размещайте backend API на том же origin или настройте CORS-политику backend,
чтобы разрешить origin документа iframe. CORS настраивается на backend, это не
параметр AppConfig.
Web Host извлекает полезную нагрузку AppConfig и переходит к полной инициализации. Полный протокол сообщений (конверт @gen2-chat и перечисление IFrameMessageType) см. в Прокси и изоляция. Это рукопожатие SetConfig специфично для ручного встраивания без фасада; модуль wippy/facade вместо этого загружает Web Host как JS-модуль.
Настройка модуля фасада
Параметры wippy/facade, формирующие приведённый выше ответ конфигурации, задаются в вашем _index.yaml. Реальный пример из 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}}'
Полный список доступных параметров и их умолчаний см. в справочнике модуля фасада.
Передача import map в Host
Получатель принимает iframe SetConfig только от физического родительского окна. Если origin родителя доступен, он также сравнивает с ним event.origin. В Web Fragment сообщение должно быть адресовано собственному fragmentId этого экземпляра.
Требование фасада называется import_map; /facade/config возвращает его как cfg.importMap. Перед импортом модуля Host shell передает это значение общему bootstrap import map. Затем он передает importMap: cfg.importMap в начальную конфигурацию initWippyApp. См. Последовательность запуска. Изменение import map не влияет на уже открытый документ. Перезагрузите или создайте документ заново, чтобы использовать новую карту.
Минимальные версии для прямой интеграции с Host и для пути через Facade указаны в разделе Модуль Facade и в Последовательности запуска.
UI policy in 1.0.62
Web Host 1.0.62 передаёт следующие настройки чата дочерним приложениям. hostConfig относится к оболочке Web Host и никогда не входит в конфигурацию дочерних приложений. В управляемой компоновке нет встроенного чата по умолчанию. Приложения, которые полностью работают на дочернем уровне, используют поля верхнего уровня или переопределения пакета.