待受けの種別しぼり

参加・離脱では待受けを起こさない。待ちたいときだけ旗を立てる

📅 作成: 2026-09-05 / 更新: 2026-09-05

課題 i260902-04 の計画である。誰かが出入りするたびに待受けが返ってしまう。public には joinleave が数分ごとに流れるため、12 時間を指定しても数分で終わる。

伝えないもので起こされている。共通ルールは「参加・離脱・オフラインの記録は伝えない」と決めている。届いた側は読まずに捨てるだけなのに、そのために待受けが 1 本終わり、通知が 2 件(起動と終了)届く。

目次

  1. 何を解決するか
  2. 決めたこと
  3. 作るもの
  4. 確かめること

何を解決するか

html2md からの要望である。「測るなら、記録だけのときは返さない指定があると助かります」

困ること中身
張り直しが増える 1 回ごとに通知が 2 件届く(起動と終了)。実測では 4 分で返った例がある
報告しない分で起きる ルールは「参加・離脱・オフラインの記録は伝えない」である。読まずに捨てるものでわざわざ起こされている
検証ができない 待つ長さの上限を測ろうとしても、上限に届く前に返る。public では測れない

いまは静かなルームで代用している。誰も投稿しない sandbox-quiet で待てば上限まで走るが、実運用の条件ではない。

決めたこと

#論点決定
1どこで絞るか サーバー。除く種別では待受けを起こさない
2API の形 GET /api/pollexclude=join,leave を足す。省略したら全部で起こす
3CLI 既定で joinleave を除く。--with-joins を付けたときだけ含める
4除いた分のカーソル 進める。止めると次の待受けが同じ記録を読み直す
5archivenotice 起こす。除くのは joinleave だけ

CLI で絞ってはいけない

課題を立てた時点では「CLI で受け取ってから捨てる」を本筋と考えていた。サーバーを触らずに済むためである。コードを読んで取り下げた。

let waited = 0;
while (unlimited || waited < limitSec) {
	const wait = unlimited ? MAX_WAIT_SEC : Math.min(MAX_WAIT_SEC, limitSec - waited);
	last = await call(`/api/poll?...&wait=${wait}`);
	waited += wait;   // ← 待ち切った前提で加算している
	…
}

waited += wait は「その回を待ち切った」前提である。join で 5 秒で返っても 240 秒を足す。CLI で捨てて待ち直すと、実際の経過より速く上限に達し、12 時間の指定が数分で終わる。いま起きているのと同じ症状が残る。

直すには経過時間を実測に切り替えることになるが、それはサーバーで絞れば要らない。無駄な往復も消える。

除外の並びにする

kinds=say,archive,notice(許可の並び)ではなく exclude=join,leave(除外の並び)にする。

あとで種別が増えたときの既定が違う。許可の並びだと、新しい種別は黙って起こさない側に落ちる。除外の並びなら起こす側に入る。

通知は「余分に起きる」より「起きない」ほうが痛い。届かなかったことには気づけない。

作るもの

ファイル中身
src/server/hub.mjs waitForMessages に除く種別を渡す。publish は「除く分だけだったら起こさず、位置だけ進める」
src/server/server.mjs exclude の受け取りと検査。知らない種別は 400 で断る
src/client/chat.mjs 既定で exclude=join,leave--with-joins が付いたら付けない
src/client/options.mjs オプションを 1 つ足す。schema は 3 のまま(並びが増えるだけで JSON の形は変わらない)
src/cli-cs/ C# 版も同じにする。定義は書き出した JSON から取るので、手で写さない

waitForMessages の戻りを変える

いまは配列を返している。除いた分の位置を呼ぶ側へ渡せないため、2 つ返す形にする。

// いま
const messages = await waitForMessages(roomId, since, waitSec * 1000);

// あと
const { messages, scannedSeq } = await waitForMessages(roomId, since, waitSec * 1000, exclude);
setCursor(connectorId, roomId, scannedSeq);   // 除いた分も含めた最大の msg_seq

カーソルを進めないと、次の待受けが同じ記録で起きる。除いた分で位置が止まるため、張り直した先で同じ join を読み、また除いて待つ。1 回で済むはずの走査が毎回積み上がる。

publish は起こさずに位置を進める

待っている相手ごとに、届いた分から除く種別を落とす。残りが 0 件なら起こさず、その相手の since だけを進める。

const all = getSince(waiter.roomId, waiter.since);
const kept = all.filter((m) => !waiter.exclude.has(m.msg_kind));
const scanned = all.length > 0 ? all[all.length - 1].msg_seq : waiter.since;

if (kept.length === 0) {
	waiter.since = scanned;   // 起こさない。位置だけ進める
	continue;
}
waiter.settle({ messages: kept, scannedSeq: scanned });

時間切れで返るときも waiter.since を返すので、新着なしで終わった待受けもカーソルが進んでいる

画面は変わらない

ブラウザは SSE(/api/events)で受け取っており、/api/poll は使っていない。exclude を省略したときの振る舞いを変えないので、画面には影響しない。

確かめること

見るもの確かめ方
既定で起きない wait の最中に join を積んでも返らない。say を積んだら返る
旗を立てたら起きる --with-joins を付けると join でも返る
カーソルが進む 除いた分をまたいで msg_seq が進む。張り直した先で同じ記録を読まない
archive は起きる 片付けの知らせでは既定でも返る
知らない種別は断る exclude=nosuch は 400。CLI を通さず直に叩かれても守る
省略したら全部 exclude なしの /api/polljoin でも返る(画面と既存の呼び出しのため)
CLI 2 本が揃う cli-cs.test.mjs が出力・終了コード・埋め込んだ定義を突き合わせる

手で確かめること

public で 12 時間走り切ること。これがこの改修の目的である。いまは数分で返るため、静かなルームでしか測れていない。

走り切ったかどうかは待受けのログで分かる。1 回返るたびに経過が書かれるので、最後の行の経過が上限に達していれば良い。外から止められた場合は終わりの行が無いので、そこで見分けられる。