困っていたこと
iPhone のホーム画面に追加した管理画面からつぶやきを投稿すると、ときどき「通信エラーが発生しました」と出て、書いた文章を最初から書き直すことになっていた。
ログインのセッション(ログインしたままの状態を覚えておく仕組み)には有効期限がある。これはセキュリティのために必要なので変えない。困っていたのは「期限が切れたこと」ではなく、「書いた文章が消えること」と「理由が分からない表示になること」の2つだった。
なぜ「通信エラー」と出ていたのか
セッションが切れていると、サーバーは投稿を受け付けずに、ログイン画面へリダイレクト(別のページへ自動で移動させること)する。
投稿ボタンは画面を移動せずにデータを送る fetch という仕組みを使っている。fetch はリダイレクトを自動でたどるので、返ってくるのは「投稿の結果」ではなく「ログイン画面の HTML」になる。画面側は JSON(プログラム同士でやり取りするデータの形式)が返ってくる前提で読もうとするため、ここで失敗して「通信エラー」と表示していた。
そのあとログインし直したり、iPhone がアプリを裏に回したあとに画面を読み込み直したりすると、入力欄の文章は消えてしまう。
直したこと
本文を端末に自動保存する
入力欄の文章を、ブラウザの localStorage(その端末のブラウザの中にだけデータを残せる保存場所)に保存するようにした。
- 入力が止まって 0.5 秒たったら保存する(1文字ごとに書き込まない)
- アプリを切り替えたとき(画面が裏に回ったとき)にもすぐ保存する
- 投稿ボタンを押した直後にも保存する
- 画面を開いたときに入力欄が空で下書きがあれば、入力欄に戻して「下書きを復元しました」と知らせる
- 投稿に成功したときだけ下書きを消す
保存するのは本文だけにした。画像や情報カードは容量が大きく、選び直すこともできるため対象外にしている。
セッション切れを見分けて案内する
返ってきた応答が「ログイン画面へのリダイレクトだった」「JSON ではなかった」「401(認証が必要)だった」のどれかなら、通信エラーではなくセッション切れとして扱う。
その場合は「ログインの有効期限が切れました。書いた内容はこの端末に保存してあります」と表示し、ログイン画面を開くか選べるようにした。ログインし直して投稿画面に戻ると、文章が自動で復元される。
本当にネットにつながらないときも、「文章は保存してあります」と添えて表示するようにした。
セキュリティ面で気をつけたこと
- 認証の仕組みは変えていない: セッションの有効期限やリダイレクトの動きはそのまま。画面側の表示と保存だけを変えた
- 下書きは端末の外に出さない: サーバーにも URL にも送らない。保存先はその端末のブラウザの中だけ
- 残しっぱなしにしない: 投稿に成功したら消し、7日より古い下書きは期限切れとして捨てる。保存する文字数にも上限を付けた
- HTML として扱わない: 復元するときは入力欄の値として入れるだけなので、保存された文字列がプログラムとして動くこと(XSS)はない
- 壊れたデータは捨てる: 読み込んだ下書きの形が正しくないときは使わずに消す
- ログイン後の移動先は固定: ログイン後に元の画面へ戻す機能は付けていない。移動先を URL で指定できるようにすると、外部サイトへ飛ばされる「オープンリダイレクト」の入口になりうるため