セッションライフサイクル
この文書は、セッション管理の実装を クライアント側とサーバー側に分けて説明します。
1. 管理責務の分離
| 管理面 | 主な保存先 | 主な責務 |
|---|---|---|
| クライアント共有 | localStorage (starkBallotSession, starkBallotSessionSchemaVersion) | 画面遷移フェーズ、クライアント TTL、検証継続状態、UI 復元、スキーマ無効化 |
| クライアントタブ単位 | sessionStorage (starkBallotSessionLock) | タブごとの session identity lock、stale tab の fail-closed |
| サーバー | VoteStore 実装(Mock/File/Amplify) | 投票データ、掲示板、集計結果、検証結果 |
クライアントとサーバーのセッション対応付けには sessionId と X-Session-Capability(署名トークン)が使われます。
ヘッダー / path / query の使い分けはエンドポイント一覧を参照してください。
補足:
ensureClientStorageSchema()はstarkBallotSessionSchemaVersionを確認し、不一致時はstarkBallotSession、stark-ballot-knowledge、starkBallotSessionLockをまとめてクリアします。
2. クライアント側フェーズ
クライアントセッション(src/lib/session/client.ts)のフェーズは以下の 3 つです。
votingfinalizingverifying
主な遷移トリガー:
POST /api/session後にgenerateSessionId(sessionId, capabilityToken)でvoting開始aggregate画面で非同期集計のpending/runningを検知すると identity-scoped helper でphase: 'finalizing'を保存aggregateまたはresult画面で canonical(現行契約で受理可能な集計スナップショット)なfinalizeResultを保存できると identity-scoped helper でphase: 'verifying'へ進む/resultから/verifyへ進む時はverificationRequestedAtを保存し、POST /api/verification/runを必要に応じて先行起動する/verifyの継続判定:verificationRequestedAtと canonical なfinalizeResultの両方がそろっていれば継続扱い(hasContinuationAuthority)- 上記がなくても、サーバー返却の STARK 状態が
not_run以外なら進行できる hasContinuationAuthority不成立かつ STARK がnot_runの場合はブロックする
3. クライアント TTL 実装
SESSION_PHASE_TIMEOUTS_MS:
voting: 30 分finalizing: 30 分verifying: 24 時間
TTL 更新の実装ポイント:
generateSessionId(...): 新規作成saveSessionData(...)/saveSessionDataForIdentity(...): フェーズを加味してexpiresAtを再計算updateLastActivity(...)/updateLastActivityForIdentity(...): 現在フェーズでexpiresAtを再延長
期限切れ判定:
checkTimeout()、getSessionData*()、saveSessionData*()、updateLastActivity*()は有効期限超過を検出するとclearSession()を実行
補足:
- 専用の heartbeat API はなく、verify 画面でクライアントが 60 秒間隔で
updateLastActivityForIdentity()を呼びローカル TTL を延長します。 sanitizeSessionData()は非 canonical なfinalizeResultを保存しません。phase: 'verifying'なのに有効なfinalizeResultがない場合はverificationRequestedAtを削除し、phaseをvotingに巻き戻します。
4. サーバー側セッション状態
サーバーは SessionData に以下を保持します。
- 投票(
votes,userVoteIndex,botCount) - 集計状態(
finalizationState) - 集計結果(
finalizationResult) - 最終活動時刻(
lastActivity)
finalizationState.status は以下を取り得ます。
pendingrunningsucceededfailedtimeout
5. サーバー側 TTL / 失効の実装差分
サーバー側の失効挙動はストア実装で異なります。
| ストア | 失効/TTL の実装 |
|---|---|
MockSessionStore | getActiveSessionCount() 呼び出し時に lastActivity から 5 分超を掃除 |
FileMockSessionStore | getActiveSessionCount() 呼び出し時に同様に 5 分超を掃除 |
AmplifySessionStore | TTL 属性を保存。初期は AMPLIFY_DATA_TTL_SECONDS(既定 1800 秒)、finalized を伴う保存では AMPLIFY_DATA_VERIFICATION_TTL_SECONDS(既定 86400 秒)へ延長 |
補足:
- Amplify の TTL 延長対象は
finalizeSession(),markFinalizationSucceeded(),saveBitmapData()などfinalized: trueを伴う保存。finalizationResultのみの後続更新では延長 TTL は適用されず、通常 TTL で再保存されます。
重要事項:
/api/sessionはMAX_SESSIONSを参照し、上限到達時はSESSION_LIMIT_EXCEEDEDを返します。
6. セッションヘッダースコープ
エンドポイントごとの X-Session-ID / X-Session-Capability の要否はエンドポイント一覧を参照してください。
POST /api/session のみヘッダー不要で、それ以外の公開 API は少なくとも一方が必須です。
7. マルチタブ時の実務上の注意
localStorage は同一オリジンで共有されますが、現行実装は sessionStorage の tab lock を併用し、別タブがセッションを差し替えたら stale tab を fail-closed にします。
代表的な結果:
- 片方のタブで投票済み後、別タブで再投票すると
ALREADY_VOTED - 片方のタブで集計完了後、別タブで再集計すると
SESSION_ALREADY_FINALIZED - aggregate / result / verify / bot progress の stale tab は
storageイベントを検知し、sessionReplacedとして停止します - セッション作成を並行すると
starkBallotSession自体は共有更新されますが、先に開いていたタブはstarkBallotSessionLockと不一致になり継続利用できません