ファサードのエントリポイント
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 シェル(オプトイン、早期アクセス): 宣言的なマルチパネルレイアウト。
ページを簡略化すると次のようになります:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="app"></div>
<script src="https://web-host.wippy.ai/<release-tag>/module.js"></script>
<script>
fetch('/facade/config')
.then(r => r.json())
.then(config => {
window.initWippyApp(config, '#app')
})
</script>
</body>
</html>
ページは自身の設定を取得し、モジュールのinit関数に渡します。ホストはページにマウントし、ルーティングとブラウザ履歴を引き継ぎ、完全な初期化を進めます。
fetchのパスに関する注意。
/facade/configはファサードが公開ルーターに登録するパスであり、ページが実際に取得するURLにはそのルーターのプレフィックスが含まれます。例のプレフィックス/api/publicの場合は/api/public/facade/configになり、これが出荷されるファサードページが実際に取得するものです。ここでのインラインのfetch('/facade/config')の断片は、読みやすさのために短縮しています。
設定のフロー
設定のフローは2ステップです:
- ページのインラインJavaScriptが、ページと同一オリジンで
GET /facade/configを呼び出します。このエンドポイントはwippy/facadeが公開ルーターに登録します。 - レスポンスを受け取ると、ページは設定オブジェクト全体を、読み込んだJSモジュールのinit関数(
window.initWippyApp(config, rootContainer?))に渡します。
Web Hostは設定オブジェクトから AppConfig のペイロードを抽出し、完全な初期化を進めます。これ以降、ページのスクリプトは受動的になります。すべてのユーザー操作は、マウントされたホストの内部で行われます。
このパターンにより、CDNでホストされるバンドルにデプロイ固有のURL、トークン、ブランディングが含まれることはありません。バンドルはどのデプロイでも同一です。異なるのは設定のペイロードだけです。
シェルのフィールドと子の
AppConfig。/facade/configのレスポンスは両方を運びます。facade_url、iframe_origin、iframe_url、login_pathのようなフィールドは、埋め込み側のページが自身を組み立てるために消費するシェルレベルのフィールドであり、子のAppConfigの一部ではありません。ホストが実際に初期化に使うAppConfigは、auth、env、theming、hostConfig、context、および以下に記載する他のフィールドです。
/facade/config のレスポンス
設定エンドポイントは、シェルレベルのフィールドと子の AppConfig の両方を運ぶJSONオブジェクトを返します。ファサードのページはそれをホストモジュールのinit関数に渡します。手動のiframe埋め込みでは、代わりに AppConfig の部分をPostMessageで配信します(後述)。すべてのフィールドは、wippy/facade がそのモジュールパラメータと実行中の環境から組み立てます:
{
"$schema": "wippy-context-2.0",
"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",
"auth": {
"token": "eyJ...",
"expiresAt": "2026-06-01T12:00:00Z"
},
"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",
"axiosDefaults": {},
"apiRoutes": {},
"tanstack": { "lists": { "refetchOnWindowFocus": true } },
"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",
"showAdmin": true,
"allowSelectModel": false,
"startNavOpen": false,
"hideNavBar": false,
"disableRightPanel": false,
"hideSessionSelector": false,
"additionalNavItems": [],
"stateCache": {},
"allowAdditionalTags": [],
"chat": {}
},
"context": {
"resourceId": "root",
"resourceType": "page",
"route": "/"
}
}
フィールドのリファレンス
シェルレベルのフィールド — 埋め込み側のページが自身を組み立てるために消費します。子の 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 |
設定契約のバージョン("wippy-context-2.0")。 |
auth |
AppConfig.auth として注入される、ランタイムのbearerトークンと有効期限。 |
env |
トップレベルの AppConfig.env として注入されるランタイムのURL。 |
routePrefix |
子アプリに転送されるAPIのURLプレフィックス。 |
axiosDefaults |
子アプリに転送されるAxiosインスタンスのデフォルト。 |
apiRoutes |
個々のAPIエンドポイントのパスをオーバーライドする(トップレベルの AppConfig フィールド)。 |
tanstack |
TanStack Queryのデフォルト。グローバル + 役割ベースのカテゴリごと(content/lists)。トップレベルの AppConfig フィールド。ホストのデフォルトは refetchOnWindowFocus:false。 |
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 で盲目的に設定を送るのではなく、その要求を待ち受けます:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<iframe id="wippy" style="width:100%;height:100vh;border:none"></iframe>
<script>
fetch('/facade/config')
.then(r => r.json())
.then(config => {
const iframe = document.getElementById('wippy')
// 子の @gen2-chat 設定要求を待ち受け、それに応答する。
window.addEventListener('message', (event) => {
if (event.origin !== config.iframe_origin) return
const msg = event.data
if (msg?.type === '@gen2-chat' && msg.action === 'get-config') {
iframe.contentWindow.postMessage(
{ type: '@gen2-chat', action: 'set-config', ...config },
config.iframe_origin
)
}
})
// iframe_url には既に ?waitForCustomConfig が含まれている
iframe.src = config.iframe_url
})
</script>
</body>
</html>
?waitForCustomConfig クエリパラメータ(iframe_url に既に含まれています)が鍵となるシグナルです。これはWeb Hostに初期化を一時停止するよう伝えます。アプリはマウントしますが、set-config メッセージを受け取るまで、認証の解決やルートの読み込みを意図的に試みません。これがないと、Web HostはURLパラメータやデフォルトから認証トークンを読もうとしますが、それは埋め込みデプロイには適切ではありません。
ハンドシェイクは @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 }で応答します。
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}}'
利用可能なパラメータの完全な一覧とそのデフォルトについては、ファサードモジュールのリファレンスを参照してください。