JavaScriptで超お手軽香川県民認証をつくる

## 前文
なんかヤバげな条例がうどんでおなじみのあの県で可決されてしまったのでノリと勢いと恨みにまかせてウェブサイトに県民認証を追加します。

明日のことを気にしてばっかじゃ毎日タイクツしちゃいますのでサクッと対応して気の滅入る条例のことは一旦忘れることにしましょう。

しれっと初投稿です。よろしくお願いします。割とひどい記事かもしれません。

## 前提
すでにあるサイトにちょっとだけ手を入れて認証ぶち込みてえなというのを想定します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144262/1405d6a8-0ea7-adec-004b-22ac37609753.png)

https://github.com/project-alisa/Ouranos

このサイトのシステム(Ouranos)はLaravelで構築しており、viewはbladeで書いています。

## 求められそうな仕様
* どのページにランディングされてもちゃんと確認する
* 県民かどうかを聞く
* 県民なら弾く
* 県民じゃないならいらっしゃいませ

まぁやってることはえっちなサイトの年齢認証と同じですね。

## localStorageが使えそう
localStorageとは[Web Storage API](https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API)という所謂キーバリューストアの一つで、オリジンごとにデータを保存できる仕組みです。

https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API

### どう使うのか
コード例は `localStorage` ですが `sessionStorage` も同様に使えます。

|処理|コード例|
|:--:|:--|
|取得|`localStorage.getItem('key')`|
|変更|`localStorage.setItem('key','value')`|
|削除|`localStorage.removeItem('key')`|
|全削除|`localStorage.clear()`|

> **Notice :** 一応 `localStorage['key']` や `localStorage.key` のような記法での参照や変更も可能ですが[MDN](https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API)では `getItem()` 等のメソッドを使う方法が推奨されています

なるほどシンプルではありませんか。

ただし制限があり**保存できる値は文字列に限られます**。
例えばオブジェクトをぶち込んだ場合、文字列に変換され `[Object object]` が返ってきます。悲しい。
JSONやオブジェクトを入れたいならば `JSON.stringify()` などで文字列化してからストアする必要があります。

## 作る
Laravelのbladeに組み込みますがやってることはHTMLとJSだけですので基本的にどんなサイトでも通用します。

見やすさ(シンタックスハイライト)重視で.htmlにしていますが実際には.blade.phpです。

```wall-kagawa.html
<dialog id="wall-kagawa-dialog" style="height: 500px">
    <div class="msgbox" style="width: 750px;">
        <div class="msgboxtop">確認</div>
        <div class="msgboxbody" style="text-align: center">
            <span style="font-size: 60px">&#x26a0;</span>
            <p style="font-size: 23px">あなたは香川県民ですか？</p>
            <hr>
            <p>
                当サイトは、香川県ネット・ゲーム依存症対策条例第11条各項に基づき、<br>
                香川県内からのアクセスはすべてお断りしています。<br>
                なお、この確認は当サイトが同条例に対し賛同を示すものではありません。
            </p>
            <p style="font-size: 13px;">
                This dialog is displayed based on the Kagawa Prefectural Internet and Game Addiction Measures Ordinance.<br>
                If you are not a Kagawa citizen, press "いいえ" to continue.
            </p>
            <div id="localStorageUnavailable">
                <hr>
                <p style="font-size: 14px;">
                    あなたの使用しているブラウザは何らかの理由でlocalStorageを利用できません。<br>
                    ページ遷移ごとにこの確認ダイアログが表示されます。<br>

                </p>
            </div>
        </div>
        <div class="msgboxfoot">
            <a href="javascript:enableWallKagawa()" class="button jw">はい</a>
            <a href="javascript:disableWallKagawa()" class="button jw">いいえ</a>
        </div>
    </div>
</dialog>
<dialog id="wall-kagawa-blocker" style="height: 500px">
    <div class="msgbox" style="width: 750px;">
        <div class="msgboxtop">警告</div>
        <div class="msgboxbody" style="text-align: center">
            <span style="font-size: 60px">&#x26d4;</span>
            <p style="font-size: 23px">閲覧を制限しています</p>
            <hr>
            <p>
                当サイトは、香川県ネット・ゲーム依存症対策条例第11条各項に基づき、<br>
                香川県内からのアクセスはすべてお断りしています。<br>
                なお、この確認は当サイトが同条例に対し賛同を示すものではありません。
            </p>
            <p style="font-size: 13px;">
                This dialog is displayed based on the Kagawa Prefectural Internet and Game Addiction Measures Ordinance.
            </p>
        </div>
        <div class="msgboxfoot">
        </div>
    </div>
</dialog>
<style>
    dialog#wall-kagawa-dialog::backdrop, dialog#wall-kagawa-dialog + .backdrop{
        background: rgba(0,0,0,0.8);
    }
    dialog#wall-kagawa-blocker::backdrop, dialog#wall-kagawa-blocker + .backdrop{
        background: black;
    }
</style>

<script>
    /**
     * WebStorageAPI 使用可否検証関数
     * https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
     * */
    function storageAvailable(type) {
        var storage;
        try {
            storage = window[type];
            var x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        }
        catch(e) {
            return e instanceof DOMException && (
                // everything except Firefox
                e.code === 22 ||
                // Firefox
                e.code === 1014 ||
                // test name field too, because code might not be present
                // everything except Firefox
                e.name === 'QuotaExceededError' ||
                // Firefox
                e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
                // acknowledge QuotaExceededError only if there's something already stored
                (storage && storage.length !== 0);
        }
    }

    const wallKagawa = document.getElementById('wall-kagawa-dialog');
    dialogPolyfill.registerDialog(wallKagawa);
    const wallKagawaBlocker = document.getElementById('wall-kagawa-blocker');
    dialogPolyfill.registerDialog(wallKagawaBlocker);
    wallKagawa.addEventListener('cancel',function(event){
        event.preventDefault();
    });
    wallKagawaBlocker.addEventListener('cancel',function(event){
        event.preventDefault();
    });

    function disableWallKagawa(){
        localStorage.setItem('wallkagawa',false);
        wallKagawa.close();
        console.info('WallKagawa disabled!');
    }
    function enableWallKagawa(){
        localStorage.setItem('wallkagawa',true);
        wallKagawa.close();
        wallKagawaBlocker.showModal();
    }

    if(storageAvailable('localStorage')){
        console.info('localStorage available.');
        document.getElementById('localStorageUnavailable').setAttribute('style','display:none;');
    }

    if(localStorage.getItem('wallkagawa') === "true"){
        wallKagawaBlocker.showModal();
        console.error('WallKagawa enabled!');
        console.info('Are you not Kagawa citizen? run %clocalStorage.removeItem(\'wallkagawa\')','color:skyblue')
    }else if(localStorage.getItem('wallkagawa') !== "false"){
        wallKagawa.showModal();
    }else{
        console.info('WallKagawa is already disabled.');
    }


</script>

```

できました

あとは適宜viewにぶち込んでやるだけです。今回は大まかなレイアウトを定義したbladeがありましたのでそこで `@include('wall-kagawa.blade')` しました

### やってること
#### 1. ダイアログを用意する
HTMLでサクッと組みましょう

dialog要素は現時点ではChromium系など一部でしか動きませんので、Firefoxなどに対応するためには[dialog-polyfill](https://github.com/GoogleChrome/dialog-polyfill)を導入する必要があります。[MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/dialog)も参照してください。

#### 2. WebStorageAPIが使えるかどうかをチェック
[MDN](https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#Feature-detecting_localStorage)にコード例があるのでありがたくコピペします。

使えない場合はサーセン文言も入れておきます。Safariのプライベートモードは事実上localStorageが使えないようなのでコケるはずです。一応「あんたのブラウザだめやでー」というメッセージも置いておきます。後で試してみよ。

#### 3. ESCキーキャンセルを無効化する
dialog要素はデフォルトではEscキーを押すなどするとキャンセルされる、つまりダイアログが閉じられるのですが、こいつが閉じられては意味がありません。
Escキーなどでダイアログを閉じるとき`close`イベントが発火しますのでイベントリスナで `preventDefault()` します。

#### 4. 状況に応じてWallKagawaを発動する
3つの状態を想定します

1. `localStorage.getItem('wallkagawa') === "true"` : WallKagawa発動状態  
  読み込み時点でWallKagawaを全面に表示します。
1. `localStorage.getItem('wallkagawa') !== "false"` : WallKagawa待機状態  
  ダイアログを提示し、県民かそうでないかを聞きます。ここでの選択に応じてlocalStorageに値をストアし以降の状態を変化させます。
1. `localStorage.getItem('wallkagawa') === "false"` : WallKagawa無効状態  
  県民でないと確認が取れたのでWallKagawaはおやすみです。

### 実際に動作している様子
#### 確認ダイアログ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144262/f78b1b38-8e0a-2336-ec0d-cbc18209a06e.png)

#### いいえを押した状態(WallKagawa無効化)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144262/0f93e5af-c6a4-d035-fa93-05e0934e6c68.png)
みて！環ちゃんがピースしているよ かわいいね

#### はいを押した状態(WallKagawa有効化)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144262/2a157460-47b4-281a-9b1e-26a89e2860eb.png)
香川県議会が条例を可決したせいで、香川県民は環ちゃんのピースを見れなくなってしまいました
香川県議会のせいです
あ～あ

## さいごに
HTMLとJSだけでシンプルにやりました。

本来ならばMiddleware噛ませてHTTP451でも返したほうがいいんでしょうがまぁ香川県に対してだけなのでいいやってことにします。嘘です。あとでもうちょっと考えます。

ミリオンをメインに追っかけているPですが個人的にはデレマスの三好紗南ちゃんがどうなるのかが気がかりです。