セッションタイムアウトで入力内容を消さない警告UIと強制保存の設計(デモ付き)

はじめに

セッションタイムアウトの警告モーダルは、「あとN秒でログアウトします」の表示自体はすぐに作れます。ところが、警告を出すところまでで設計を止めると、次の3つの場面で入力していた内容が消えます。

  1. 警告を無視してカウントダウンが0になると、そのままログアウトされて入力内容はまとめて消える
  2. 警告に気づいて「今すぐログアウト」を選んでも、その時点の入力が保存されていなければ同じように消える
  3. 入力が止まってから一定時間後にまとめて保存する自動保存に任せていると、ログアウト直前の数秒に打った文字は保存が間に合わない

症状は3つですが、原因は1つです。警告UIだけを入力保護の手段にしているからです。この記事では、セッションタイムアウト警告(カウントダウンモーダル)で実装したカウントダウンの仕組みを前提に、タイムアウトが確定する瞬間に入力を守る強制保存の設計と、再ログイン後の復元UIを扱います。警告を出すタイミングや無操作の判定基準そのものは次の章で基準だけ示し、記事の主眼は「警告を出したあと、入力をどう守るか」に置きます。なお、この記事とデモは1つのタブで操作している場合を対象にします。同じフォームを複数のタブで開いている場合の注意点は、後半のエッジケースの章で触れます。

警告タイミングと無操作判定の基準

警告をいつ・どう出すかは、overlay-session-timeout-1で実際に検証した基準をそのまま使います。詳しい実装はそちらのサンプルソースを参照してください。ここでは要点だけ表で示します。

論点 目安・実装 補足
警告を出すタイミング 残り5〜30分が一般的 機密度の高い業務アプリほど短くする。デモでは体感確認のため数秒に短縮している
操作とみなす範囲 クリックとキー入力のみ mousemoveを含めると、カーソルが画面内を通過しただけでタイマーがリセットされ、警告が出にくくなる
カウントダウンの実装 終了予定時刻との差分計算 setIntervalの単純な減算だと、バックグラウンドタブでタイマーが間引かれたときにズレる

ここで押さえておきたいのは、警告を正しく表示することと、入力内容を保存することは別の処理だという点です。次の章から「警告を出したあと、入力をどう守るか」に絞って設計を詰めます。

タイムアウト直前の強制保存という設計

フォームの下書き保存では、キーを打つたびに保存するのではなく、入力が止まってから一定時間後にまとめて保存する方式がよく使われます。JavaScriptではdebounceと呼ばれる方式で、この形が扱いやすい理由はlocalStorageのフォーム自動保存はどう設計するかで解説した通りです。ただしこの方式には、保存されない時間があります。debounceの待機時間中(例えば500ms)に警告モーダルのカウントダウンが0になってログアウトが確定すると、直前の入力は保存処理が実行される前に失われます。

対策は単純です。ログアウトを確定する処理の中に、通常のdebounce保存とは別に、強制保存の呼び出しを1回差し込みます。

function forceLogout() {
  // 通常のdebounce保存を待たず、ログアウトの直前に確実に1回保存する
  saveDraft();
  performLogout();
}

overlay-session-timeout-1のカウントダウンでは、警告終了予定時刻との差分がゼロ以下になったタイミングでlogout()を呼んでいます。この呼び出しの直前に下書き保存を差し込むだけで、debounceの間隔に関係なく、ログアウトが確定する瞬間の入力内容を保存できます。カウントダウンが0になった場合だけでなく、「今すぐログアウト」ボタンを押した場合も同じ処理を通るようにしておくと、どちらの経路でも入力が守られます。ただし、localStorageが使えない環境や保存自体が失敗するケースはあるため、保存に失敗してもログアウト処理は止めない作りにします(後述のエッジケース参照)。

保存先の選び方や、機密データを保存対象から除外する設計はlocalStorageのフォーム自動保存はどう設計するかの内容をそのまま流用します。ここで新しく決めるのは「いつ保存するか」に、通常のdebounceに加えて「タイムアウト確定の瞬間」を1つ足す、という点だけです。

再ログイン後の復元バナー

強制保存した下書きを、再ログイン直後に自動的にフォームへ反映するのはおすすめしません。再ログイン後に入力し直していた内容や、別の画面で更新済みのデータを、古い下書きで黙って上書きしてしまうからです(この事故の詳細はlocalStorageのフォーム自動保存はどう設計するかで扱っています)。ここでも自動復元ではなく、バナーで確認する形にします。

function onReloginSuccess() {
  var saved = loadDraft();
  if (saved) {
    showRestoreBanner({
      onRestore: function () { fillForm(saved); },   // 押されたときだけ戻す
      onDiscard: function () { clearDraft(); }        // 破棄なら消す
    });
  }
}

実装は下書き自動保存フォーム(localStorage復元)の復元バナーと同じ形にそろえます。違うのは保存が発生するタイミングだけです。form-draft-1は入力中のdebounceで保存しますが、この設計はタイムアウト確定時にも保存するため、セッション切れを挟んでも直前の入力が残ります。

デモ

タイムアウト直前の強制保存から、再ログイン後の復元までを実際に触って確認できます。短時間で試せるように、無操作時間とカウントダウンはそれぞれ8秒に短縮しています。次の手順で操作してください。

  1. テキストエリアに文字を入力する
  2. 8秒間なにも操作せずに、警告モーダルが表示されるまで待つ
  3. 警告を閉じずに、カウントダウンが0になるまで待つ
  4. 動作ログで、入力内容が保存されてからログアウトしたことを確認する
  5. 「再ログイン」を押す
  6. 表示されたバナーから「復元する」を押し、入力内容が戻ることを確認する

途中で「ログインを延長」を押すと、警告が閉じて無操作時間の計測がやり直されます。

このエリア内でクリックまたはキー入力をすると、無操作の時間がリセットされます。警告を表示するには、8秒間なにも操作せずにお待ちください。

最後の操作からの時間を計測しています

動作ログ
(ここに操作ログが表示されます)

このデモはあなたのブラウザのlocalStorageに一時的に書き込みます。

エッジケース

ケース 何が起きるか 対策
localStorageが使えない環境(プライベートブラウジング等)や、保存時にエラーが発生した ログアウト直前の入力内容をブラウザに保存できない try-catchでエラーを受け止め、下書き保存だけを無効化する。保存に失敗しても、警告やログアウトの処理は止めない
同じフォームを複数のタブで開いていて、片方が先にタイムアウトした 強制保存された下書きが、別のタブで入力していた内容を後から上書きする可能性がある。また、別のタブでログアウトされたことを、入力中のタブはすぐには検知できない storageイベントやBroadcastChannelで、ログアウトと下書き更新をタブ間に通知する(storageイベントの詳細はlocalStorageのフォーム自動保存はどう設計するかを参照)。この記事のデモは1つのタブでの動作のみを対象としている

まとめ

セッションタイムアウトの警告UIは、無操作の判定とカウントダウンを正しく実装しても、それだけでは入力を守れません。ログアウトを確定する処理に強制保存を1回差し込み、再ログイン後は自動復元でなくバナーで確認する。この2点を足すことで、警告を無視してタイムアウトした場合も、「今すぐログアウト」を選んだ場合も、入力内容を失わずに済みます。なお、この記事の実装は1つのタブで操作している場合を対象にしています。同じフォームを複数のタブで開く運用では、ログアウトと下書き更新をタブ間で通知する処理を別途追加してください。保存先の選び方や機密データの除外といった下書き保存そのものの設計はlocalStorageのフォーム自動保存はどう設計するかに、動く実装はセッションタイムアウト警告(カウントダウンモーダル)下書き自動保存フォーム(localStorage復元)にまとめてあります。

関連するUI事例

セッションタイムアウトと入力保護が関わる代表的なUI事例です。いずれもコピペで動くサンプルを掲載しています。