iOS 13 以降ではウェブ上でセンサー値を扱う際にユーザからの許可が必要

## はじめに
iOS 12 までは、Safari の設定からセンサー値を利用していいか権限を与えていました。
iOS 13 からは仕様が変わり、サイトごとにユーザーからセンサー値の権限を JavaScript で与える必要があります。
**※ 永続化は不可能、セッション（？）ごとに許可を取る必要がある**
<img width="240" alt="IMG_0758.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276546/36f114c8-acba-7925-c366-3cbd3100fbb5.png">
ご覧の通り項目がなくなっていますね…（iOS 13.3.1 にて）

```js
DeviceMotionEvent.requestPermission();
DeviceOrientationEvent.requestPermission();
```

このコードを実行するとセンサー値の権限を制御できるウィンドウが出てくるのですが
どうやらタップやクリックなどの意図的なユーザの行動から有効化の流れを作る必要があるみたいです…
[iOS（特に 13 以降）でのモーションセンサー有効化 - http://dotnsf.blog.jp/archives/1076737232.html](http://dotnsf.blog.jp/archives/1076737232.html)
こちらサイト様に有効化の手段は書いてあったのですが、他にも方法がないか検証してみたので共有しておきます。

## ユーザに確認を取ったが駄目 🥺 だったパターン
### 1. window.confirm
```js
if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) {
    const isPermission = confirm('このサイトでは、センサー値を扱います。');

    if (isPermission) {
        const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission();
        const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission();

        // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする
        if (isDeviceOrientationEvent && isDeviceMotionEvent) {
            sessionStorage.setItem('isPermission', 'true');
        }
    }
}
```
これでは動きませんでした。
~~これを禁止にしている Apple さんの意図が正直よくわからない~~

### 2. DOM でボタンを生成し、 JS 内で仮想クリックを行う
```js
if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) {
    const confirmElement = document.createElement('div');
    confirmElement.style.display = 'none';

    confirmElement.onclick = () => {
        const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission();
        const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission();

        // 許可したあとはまた許可が必要になるまで生成しないようにする
        if (isDeviceOrientationEvent && isDeviceMotionEvent) {
            sessionStorage.setItem('isPermission', 'true');
        }

        document.body.removeChild(confirmElement);
    }

    document. body.appendChild(confirmElement);
    window.onload = confirmElement.click();
}
```
ボタンをクリックしたイベントを呼び出して動作させればイケるんじゃないか 🤔 と思いましたが駄目でした。
ブラウザ側でタップやクリックの動作も監視しており、それも同時に実行されてないと呼び出しできない仕様になってるんですかねえ

### 3. 外部ライブラリのクリック時の動作メソッドを使う
```js
if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) {
    const tingleLinkElement = document.createElement('link');
    tingleLinkElement.rel = 'stylesheet';
    tingleLinkElement.href = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.css';
    document.head.appendChild(tingleLinkElement);
​
    tingleLinkElement.onload = () => {
        const tingleScriptElement = document.createElement('script');
        tingleScriptElement.src = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.js';
        document.body.appendChild(tingleScriptElement);
​
        tingleScriptElement.onload = () => {
            const modal = new tingle.modal({
                footer: true
            });
​
            modal.setContent('<p>このサイトでは、センサー値を扱います。</p>');
            modal.addFooterBtn('Cancel', 'tingle-btn tingle-btn--default tingle-btn--pull-right', () => modal.close());
            modal.addFooterBtn('OK', 'tingle-btn tingle-btn--primary tingle-btn--pull-right', () => {
                const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission();
                const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission();
​
                // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする
                if (isDeviceOrientationEvent && isDeviceMotionEvent) {
                    sessionStorage.setItem('isPermission', 'true');
                }

                modal.close();
            });
​
            modal.open();
        };
    };
}
```
今回、[Tingle.js](https://tingle.robinparisi.com/) というモーダルプラグインを使用した際にハマった点です。
クリックイベントも取ってるだろうし、これなら大丈夫っしょ！！って思って書きましたが駄目でした。
外部ライブラリを使いたい際は気をつけたほうが良いかもしれません。
毎回毎回、自作のコンポーネントを用意できるわけではないのでこの仕様は少し困ってしまいますね…

## 結局うまく行ったパターン
### ライブラリのメソッドは使わず addEventListener でクリックやタップ動作に対応した
```js
if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) {
    const tingleLinkElement = document.createElement('link');
    tingleLinkElement.rel = 'stylesheet';
    tingleLinkElement.href = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.css';
    document.head.appendChild(tingleLinkElement);
​
    tingleLinkElement.onload = () => {
        const tingleScriptElement = document.createElement('script');
        tingleScriptElement.src = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.js';
        document.body.appendChild(tingleScriptElement);
​
        tingleScriptElement.onload = () => {
            const modal = new tingle.modal({
                footer: true
            });
​
            modal.setContent('<p>このサイトでは、センサー値を扱います。</p>');
            modal.addFooterBtn('Cancel', 'tingle-btn tingle-btn--default tingle-btn--pull-right', () => modal.close());
            modal.addFooterBtn('OK', 'tingle-btn tingle-btn--primary tingle-btn--pull-right', () => {
                modal.close();
            });
​
            document.querySelector('.tingle-btn.tingle-btn--primary.tingle-btn--pull-right').addEventListener('click', async () => {
                const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission();
                const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission();
​
                // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする
                if (isDeviceOrientationEvent && isDeviceMotionEvent) {
                    sessionStorage.setItem('isPermission', 'true');
                }
            });
​
            modal.open();
        };
    };
}
```
動くことには動きましたが、なんか微妙に納得のいかない書き方に…
<img width="240" alt="IMG_BF5F9E0E6ABB-1.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276546/20dc89eb-32fe-5fad-ced4-8bbb1432090b.jpeg">  <img width="240" alt="IMG_0760.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276546/a12be2ab-ad55-5e24-da62-325928b594ed.png">

## 結論
**ユーザからの動作であれば何でも良いわけではなく onclick か addEventListener を使って実装しなくてはいけない**
セキュリティの観点からこういう風にサイトごとに許可を取るスタイルはしょうがないとは思うんだけど
メソッドを呼んでくれる基準がよくわからないから使う側としてはすごく困るなあと…
iOS のブラウザ（というか Safari ）はこんな感じの謎独自機能と草案の機能の実装スピードをもう少し早くしてくれればなあ… と最近思うことが多いです。
正直、ブラウザに関しては Android ブラウザのほうが圧勝だなあと思いますね 😩