コンテンツにスキップ

スペース全体

他のデモが1つのパッケージの中身を開くのに対して、このページは全部を同じ部屋に入れます。 どのパッケージ単体でも見えないもの — 境界がどこにあるか を見るためです。

2人のメンバーが1つのスペースを共有し、その中にポモドーロ・ホワイトボード・ウォッチパーティ・ チャットが同居しています。下のデモは@insession/space の createSpace を使ってサーバーを 演じます — 全 extension の state を持ち、dispatch されたアクションをそれぞれ SpaceEffect[] に変え、どのメッセージを誰に配るかはデモの effect ループが決めます。クライアントは (@insession/space-state の)createSpaceStore インスタンスを2つ作り、useSpaceState で 購読しているだけで、どちらも space.clientExtensions() 経由で同じ extension 群に配線されて います。通信はブラウザのタブから一歩も出ません。

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 が 返ってきて初めて切り替わります。だから在室メンバーの一覧が、余計な帳簿を持たずに 「誰が今どれを見ているか」を出せます。

  • ポモドーロを 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 を呼ぶのはページです。パッケージはタイマーにもスピーカーにも通知にも 触りません。

このページには「スペースが要るもの」だけを置いています。お絵かき伝言ゲームは whiteboard、再接続の挙動は @insession/ws-resilient-transport、各パッケージの全機能はそれぞれの examples ページに あります。ここで見せたいのは網羅ではなく継ぎ目です。