ファサードのエントリポイント
wippy/facade バックエンドモジュールは、Web Hostをユーザーに配信するエントリポイントです。Web HostのJSモジュールを読み込むHTMLページを配信し、認証のリダイレクトを処理し、/facade/config エンドポイントを公開し、デプロイ固有の設定をCDNでホストされるフロントエンドバンドルへ橋渡しします。バンドル自体には設定が焼き込まれていません。すべてのデプロイが、この仕組みを通じて自身の設定を提供します。
HTMLページ
ユーザーがWippyアプリケーションに遷移すると、wippy/facade がHTMLページを配信します。このページは薄いものです。CDNからWeb HostのJSモジュールを読み込み、/facade/config から返された設定でホストを初期化します。モジュールはブラウザの履歴を含むページ全体を引き継ぐため、ホストはiframe内ではなくアプリケーション全体として動作します。
ファサードは、設定された fe_mode に応じて2つのJSモジュールエントリのいずれかを読み込みます:
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')
ページは自身の設定を取得し、モジュールのinit関数に渡します。ホストはページにマウントし、ルーティングとブラウザ履歴を引き継ぎ、完全な初期化を進めます。
fetchのパスに関する注意。
/facade/configはファサードが公開ルーターに登録するパスであり、ページが実際に取得するURLにはそのルーターのプレフィックスが含まれます。例のプレフィックス/api/publicの場合は/api/public/facade/configになり、これが出荷されるファサードページが実際に取得するものです。ここでのインラインのfetch('/facade/config')の断片は、読みやすさのために短縮しています。
設定のフロー
設定のフローは2ステップです:
-
ページは同一オリジンの公開ルーターから
/facade/configを取得します。 -
シェルは Host の import map を取得し、共有 bootstrap を読み込み、Host map と
cfg.importMapを合成して登録します。その後に限りcfg.module_fileを動的 import します。シェルは返されたフィールドから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 を返します。legacy policy では wippy-context-2.0.xsd、modern policy では wippy-context-2.1.json です。古い Facade がこのフィールドを返さない場合、シェルは 2.0 XSD にフォールバックします。Web Host 1.0.66 はどちらのバージョン付きスキーマも受け入れ、正規の 2.2 形式に移行します。
/facade/config のレスポンス
このエンドポイントはシェル設定と選択された Web Host フィールドを返します。完全な AppConfig は返しません。シェルが $schema、auth、context を追加してから initWippyApp を呼び出します。Facade 0.6.46 はポリシーに応じた schema_url を返します。legacy policy では wippy-context-2.0.xsd、modern policy では wippy-context-2.1.json です。古い Facade がこのフィールドを返さない場合、シェルは 2.0 XSD にフォールバックします。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 |
Web HostバンドルのベースCDN URL。モジュールのエントリとベンダースクリプトの解決に使われます。 |
iframe_origin |
CDNの Origin ヘッダーの値。手動のiframe埋め込みでのPostMessageの targetOrigin として使われます(後述)。 |
iframe_url |
?waitForCustomConfig を含む完全なiframeの src。手動の、ファサードなしのiframe埋め込みでのみ使われます(後述)。 |
login_path |
未認証のユーザーをリダイレクトする、ページと同一オリジン上のパス。 |
子の AppConfig のフィールド — ホストのinit関数に渡され、実行中のホストが消費します:
| フィールド | 説明 |
|---|---|
$schema |
cfg.schema_url。省略時は <facade_url>/schemas/wippy-context-2.0.xsd を使用します。 |
auth |
AppConfig.auth として注入される、ランタイムのbearerトークンと有効期限。 |
env |
トップレベルの AppConfig.env として注入されるランタイムのURL。 |
routePrefix |
子アプリに転送されるAPIのURLプレフィックス。 |
axiosDefaults |
子アプリに転送されるAxiosインスタンスのデフォルト。 |
apiRoutes |
個々のAPIエンドポイントのパスをオーバーライドする(トップレベルの AppConfig フィールド)。 |
tanstack |
TanStack Queryのデフォルト。グローバル + 役割ベースのカテゴリごと(content/lists)。トップレベルの AppConfig フィールド。ホストのデフォルトは refetchOnWindowFocus:false。 |
iconify |
cfg.iconify から渡される、明示的に設定された Iconify ソース。 |
importMap |
cfg.importMap からの任意のブラウザー import map 拡張。シェルは Host モジュールの import 前に登録します。 |
theming |
3つのスコープに分かれたCSSのカスタマイズ。 |
hostConfig |
Web Hostの機能フラグとUIの設定。 |
context |
ホストの初期ページまたはアーティファクトのコンテキスト。 |
env のフィールド:
| フィールド | 由来 | 説明 |
|---|---|---|
APP_API_URL |
PUBLIC_API_URL 環境変数 |
すべてのバックエンドHTTP呼び出しのベースURL |
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 |
UIに管理機能を表示する |
allowSelectModel |
boolean | false |
LLMモデルのピッカーを表示する |
startNavOpen |
boolean | false |
読み込み時にナビゲーションサイドバーを展開する |
hideNavBar |
boolean | false |
左のナビゲーションサイドバーを完全に隠す |
disableRightPanel |
boolean | false |
右のアーティファクトパネルを無効にする |
hideSessionSelector |
boolean | false |
チャットのセッションピッカーを隠す |
additionalNavItems |
array | [] |
サイドバーに注入する追加項目 |
stateCache |
object | {} |
子iframeの状態に対するLRUキャッシュの設定 |
allowAdditionalTags |
object | {} |
HTMLサニタイザのタグのホワイトリスト(Record<string, string[]>、タグ → 許可される属性) |
chat |
object | {} |
チャットUIのオーバーライド(貼り付けによるファイル化の挙動など) |
認証のフロー
ユーザーがページを読み込んだ時点で未認証の場合、wippy/facade はHTMLページを配信する前に login_path へリダイレクトします。ログインが成功すると、ユーザーは元のURLに戻されます。認証状態がWeb Hostの設定そのものを通じて渡されることはありません。Web Hostは、認証済みのページレスポンスが auth/env に埋め込んだ認証トークンを信頼します。
設定エンドポイントは、HTMLページを配信したのと同じ認証済みセッションによって配信されるため、APP_API_URL と導出されるWebSocketのURLは、自動的にそのユーザーにとって正しいバックエンドを反映します。
モジュールのinit関数
JSモジュールのエントリは、ページに window.initWippyApp を登録します。ファサードのページは、/facade/config から取得した設定オブジェクトでこれを呼び出します。fe_mode はファサードがどのモジュールを読み込むかを選択します(compat には module.js、managed には managed-layout.js)。どちらも同じ 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内で実行したい場合のために、2つ目の埋め込みメカニズムもあります。例えば、周囲のアプリケーションからより強く分離しつつ、ページの一部だけを占有したい場合です。このモードではホストを自分で埋め込みます。ファサードはこのページを生成しません。
URLと設定を得るために、ファサードの /facade/config エンドポイントを再利用することはできます。その iframe_url(?waitForCustomConfig が既に付加されたホストの iframe.html エントリ)と iframe_origin(PostMessageの targetOrigin)は、まさにこの経路のために存在します。そのうえで、自分で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 message を待ってから import map を登録し、Host app module を import します。その時点ではアプリはまだ mount されていません。この query がない場合は通常の standalone config 経路を使います。
ハンドシェイクは @gen2-chat PostMessageプロトコルを使用します:
- 親が
GET /facade/configを取得し(または同等のAppConfigペイロードを自ら供給し)、iframe_urlを指すiframeを作成します。 - 起動中のiframeが
{ type: '@gen2-chat', action: 'get-config' }を親に送ります。 - 親の
messageリスナーが、iframe_originを宛先として{ type: '@gen2-chat', action: 'set-config', ...config }で応答します。
後で設定を変更する場合は、同じ iframe に対して完全な set-config message を
iframe_origin 宛てにもう一度送ります。Host は再 mount せずに変更を適用します。
この iframe からの API request は iframe document の origin を使います。backend API
を同じ origin で配信するか、backend の CORS policy で iframe document origin を許可
してください。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}}'
利用可能なパラメータの完全な一覧とそのデフォルトについては、ファサードモジュールのリファレンスを参照してください。
Host への import map の引き渡し
受信側は iframe の SetConfig message を物理的な親 window からのものだけ受け付けます。親 origin にアクセスできる場合は event.origin も照合します。Web Fragment では、その occurrence 自身の fragmentId を宛先に指定する必要があります。
facade requirement の名前は import_map で、/facade/config は cfg.importMap として返します。shell は Host module を import する前に共有 import-map bootstrap へこの値を渡します。その後、初期 initWippyApp config に importMap: cfg.importMap を渡します。ブートストラップの手順を参照してください。 import map の変更は既存のドキュメントには反映されません。新しいマップを使うには、ドキュメントを再読み込みするか作り直してください。
Host を直接使う場合と Facade 経由で使う場合の最小バージョンは、Facade モジュールとブートストラップの手順を参照してください。
UI policy in 1.0.62
Web Host 1.0.62 は次のチャット設定を子アプリケーションと共有します。hostConfig は Web Host ラッパー専用であり、子へのデータには含まれません。管理レイアウトには標準のチャットがありません。子として動作するアプリは、トップレベルの設定またはパッケージの上書きを使用します。