• localStorageは同一オリジンへ文字列を保存するが正本データには使わない

localStorageは同一オリジンへ文字列を保存するが正本データには使わない

公開日: 執筆:佐藤

localStorageは、画面の表示方法や入力途中の補助情報など、消えても事業記録を失わない値をブラウザーへ保存するときに使います。値は同一オリジンへ保存され、ブラウザーを閉じたあとも残せますが、利用者による削除、保存拒否、容量超過があるため、注文、権限、在庫などの正本にはしません。

基本操作は、setItem()で保存し、getItem()で取得し、不要になったキーをremoveItem()で削除することです。保存値は文字列になるため、数値や構造化した値は、保存時と取得時の変換方法まで決めます。

setItemとgetItemで一つのキーを扱う

W3CのWeb Storage Recommendationは、Storageをキーと値の組として定義しています。表示モードを保存する最小例は次のようになります。

var storageKey = 'display.mode';
var displayMode = 'standard';

try {
  localStorage.setItem(storageKey, 'compact');

  var savedValue = localStorage.getItem(storageKey);
  if (savedValue !== null) {
    displayMode = savedValue;
  }
} catch (error) {
  displayMode = 'standard';
}

getItem()は、キーがなければnullを返します。空文字列を保存した場合は''が返るため、存在確認をif (savedValue)だけで済ませると、空文字列と未保存を区別できません。

不要になった一項目は、キーを指定して削除します。

localStorage.removeItem('display.mode');

clear()は同じStorage内の全項目を削除します。自分の機能が作ったキーだけを消す場面で使うと、同じオリジンにある別機能の設定まで失わせます。通常はremoveItem()を選びます。

保存される値は文字列として読み戻す

StorageのsetItem()は、キーと値をDOMStringとして受け取ります。数値を渡しても、取得時は文字列です。

localStorage.setItem('list.pageSize', 20);

var pageSize = Number(localStorage.getItem('list.pageSize'));
if (!isFinite(pageSize) || pageSize < 1) {
  pageSize = 20;
}

複数の値を一つにまとめる場合は、JSONへ変換して保存できます。

var viewSettings = {
  mode: 'compact',
  pageSize: 20
};

localStorage.setItem('list.settings', JSON.stringify(viewSettings));

取得時はJSON.parse()を使いますが、保存済みの文字列が壊れている場合や、古い形式が残っている場合に失敗します。tryとcatchで既定値へ戻し、取得したプロパティの型と範囲も確認します。

ブラウザーへ保存した値は、利用者が編集できない設定ではありません。JavaScriptから書き換えられる入力値として扱い、権限判定や料金計算をそのまま信用しません。

同一オリジンは同じパスという意味ではない

localStorageの保存領域は、オリジンごとに分かれます。オリジンは、URLのscheme、host、portの組です。

URLの組み合わせ 保存領域 理由
https://example.com/aとhttps://example.com/b 同じ パスはオリジンを分けない
http://example.comとhttps://example.com 別 schemeが違う
https://example.comとhttps://www.example.com 別 hostが違う
https://example.comとhttps://example.com:8443 別 portが違う

同じhostを複数の制作者やシステムが共有しても、パスごとに保存領域を隔離する仕組みはありません。/shop/と/members/が同じオリジンなら、両方のスクリプトが同じlocalStorageへアクセスできます。

キーには、shop.display.modeのように機能名を含めます。ただし、名前を分けてもアクセス権を分離したことにはなりません。互いに読めない状態が必要なら、オリジンまたはサーバー側の権限を分けます。

保存できない場合を通常の分岐として扱う

localStorageが存在しても、保存が必ず成功するとは限りません。利用者が保存を許可していない場合や、利用できる容量を超えた場合、setItem()はQuotaExceededErrorを投げます。ポリシーによってlocalStorageへのアクセス自体が拒否される場合もあります。

したがって、機能の開始時に一度だけ存在を確認するのではなく、保存処理をtryとcatchで扱います。保存に失敗した場合は、次のどちらかへ戻します。

  • 表示設定なら、画面を標準値で表示する
  • 入力補助なら、保存できなかったことを利用者へ知らせ、送信処理は続けられるようにする

保存できないだけで、問い合わせや購入を完了できなくなる設計は避けます。補助機能の失敗を事業処理の失敗へ広げないためです。

永続保存と正本データを分ける

W3C仕様は、利用者の要求やセキュリティー上の理由によって保存データが削除される場合を認めています。利用者はブラウザーの操作でも削除できます。別の端末や別のブラウザーへ自動で共有される保存領域でもありません。

localStorageに置ける候補 サーバーを正本にする値
表示モード 注文と決済の状態
一覧の並び順 在庫数と価格
閉じた案内の状態 利用者の権限
送信前の入力補助 受け付けた問い合わせ

正本データは、サーバーで検証し、利用者、更新日時、処理結果とともに保存します。localStorageには、正本を再取得するための表示条件や、消えても再設定できる値だけを置きます。

個人情報や秘密情報も、同じオリジンで動くスクリプトから読めることを前提にします。保存期間が長いほど安全になるわけではありません。目的が終わったキーはremoveItem()で消します。

複数画面の同期はstorageイベントで補助する

同じオリジンを別のWindowで開いている場合、setItem()、removeItem()、clear()による変更は、影響を受けるほかのDocumentへstorageイベントとして通知されます。変更を行ったStorage自身へ同じイベントが戻る仕組みではありません。

表示モードを複数画面でそろえる用途には使えますが、サーバー上の注文処理や排他制御の代わりにはなりません。イベントを受け取った側でも値を検査し、画面の再表示にだけ使います。

実装前に、キー名、文字列への変換、未保存時の既定値、容量超過時の動作、削除時期、サーバー側の正本を一表にしてください。保存APIを呼べることより、消えた場合にも業務が止まらないことを優先します。

戻る