スペース全体
他のデモが1つのパッケージの中身を開くのに対して、このページは全部を同じ部屋に入れます。 どのパッケージ単体でも見えないもの — 境界がどこにあるか を見るためです。
2人のメンバーが1つのスペースを共有し、その中にポモドーロ・ホワイトボード・ウォッチパーティ・
チャットが同居しています。下のデモは@insession/space の createSpace を使ってサーバーを
演じます — 全 extension の state を持ち、dispatch されたアクションをそれぞれ SpaceEffect[]
に変え、どのメッセージを誰に配るかはデモの effect ループが決めます。クライアントは
(@insession/space-state の)createSpaceStore インスタンスを2つ作り、useSpaceState で
購読しているだけで、どちらも space.clientExtensions() 経由で同じ extension 群に配線されて
います。通信はブラウザのタブから一歩も出ません。
このデモが引く4つの境界
Section titled “このデモが引く4つの境界”plugin は app-state で届く。 ポモドーロとホワイトボードの状態機械はサーバー側で動き、
新しい state は { type: 'app-state', appId, state } として broadcast され、core が
apps[appId] に格納します — ここまでは plugin 記述子の有無に関係なく起きます。
plugin 固有のもの(フェーズが切り替わったときのログ行、鳴らす音)は、createSpaceStore に
渡した definePluginClient の記述子から出てきます。core は plugin の名前も文言も音色も
知りません。onAppState を呼んで、返ってきたものをそのまま流すだけです。
チャットは plugin ではなく core。 @insession/extension-chat はサーバー側で動き、その
broadcast / send-to-sender が @insession/space-state の既存の受信 case にそのまま
噛み合います。send text を押してトレースを見てください: 送信者は broadcast から除外され、
代わりに chat-ack を受け取ります。そこには、楽観的に出したローカル行に足りなかった id が
載っています。別々に書かれた2つのパッケージが、clientMsgId でちょうど噛み合います。
プレイヤーは core の外に居る。 @insession/extension-watch-party は play /
seek / queue-update を broadcast しますが、space-state にはそのどれの case も
意図的にありません。プレイヤーはスペースの別チャンネルなので、ページ側が届いた
メッセージを自分のビューへ射影します — 本物のプレイヤーコンポーネントがやっているのと
同じことです。スペースに属するために app-state を通る必要はありません。
すべてがサーバーを1周する。 スイッチャーのタブすらそうです。押すと
store.stage.change(...) が stage-change をサーバーへ送り、member-updated が
返ってきて初めて切り替わります。だから在室メンバーの一覧が、余計な帳簿を持たずに
「誰が今どれを見ているか」を出せます。
試してみること
Section titled “試してみること”- ポモドーロを start して
as Bobに切り替える。 同じ時刻が出ます。これは クライアントの state だったことが一度も無く、同じ broadcast で両方の store へ届いたapps.pomodoroだからです。 - Alice でメッセージを送って、左の在室メンバーを見る。
chatLinesの数は両方で 揃いますが、行の中身は違います — Alice のは自分のローカルエコーがchat-ackで 補正されたもの、Bob のは broadcast で届いたものです。 - スタンプを送ってトレースを読む。 ホストが先に自前の allowlist を照合して
stickerAllowedに畳んで渡すので、パッケージは URL のポリシーを一切知りません — ホストが既に決めた boolean しか見ていません。その boolean がfalseのときに 何が起きるかは、画像に本文を添えて送れる チャット のほうが 読みやすい形で見せています。 - 同じメンバーで2本キューに積む。 2本目は
maxPerUser: 1で拒否され、その拒否はsend-to-senderです — もう片方のメンバーには何も届きません。 - 何かキューに積んでタイトルを見る。 最初は
(unresolved)で積まれ、パッケージはresolve-metadata記述子を出します。ホストがそれに答えると、解決済みのタイトルが 次の broadcast で届きます。パッケージ自身は何も取りに行きません。 - 描いたあとにホワイトボードを clear する。 チャットにログ行が1本出ます。これは
core ではなく、ホワイトボード自身の
PluginClientが「消去は書き留める価値がある」と 判断した結果です。 - typing を押して待つ。 store が出すのは
typing-timerという記述子で、実際にsetTimeoutを呼ぶのはページです。パッケージはタイマーにもスピーカーにも通知にも 触りません。
ここに無いもの
Section titled “ここに無いもの”このページには「スペースが要るもの」だけを置いています。お絵かき伝言ゲームは
whiteboard、再接続の挙動は
@insession/ws-resilient-transport、各パッケージの全機能はそれぞれの examples ページに
あります。ここで見せたいのは網羅ではなく継ぎ目です。