Alexaアプリで設定したアクセス権限が保存されない問題（アクセス権限：タイマー）

# 事象

Alexaアプリからアクセス権限を設定しても権限が保存されない。（アクセス権限：タイマー）

この事象は、Timer APIを使用した[ゆでたまごタイマー](https://amzn.to/3eU6uef)スキルを公開（2020年6月12日）した直後に気づいてはいたが、有名企業の公開スキルでも同じ事象が確認できていたため、当時はスキル側の問題ではないと認識し問題視していなかった。

しかし、いっこうに事象が改善されないため、Amazon社に問合せしたところ、スキル側の問題であることが判明したので対策を実施した。

[![デモ](http://img.youtube.com/vi/nUf3FTaPd6U/0.jpg)](https://www.youtube.com/watch?v=nUf3FTaPd6U)
（動画）Alexaアプリでアクセス権限を設定したが、再度、権限を確認すると権限が外れている。

# 原因

Alexaアプリからスキルのアクセス権限を設定すると、権限を設定した瞬間にスキルが `Alexa.Authorization.Grant` というレスポンスを受け取るらしいのだが、受け取った時に呼ばれるインターセプター内で使用している `Alexa.getLocale` 関数が `Alexa.Authorization.Grant` を受け取る事を想定していなかったためエラーとなり、アクセス権限が保存される前にスキルが終了したと推測。根本原因は調査中。
（※Alexaアプリで権限を変更したときに、スキルがレスポンスを取得することを知らなかった。スマートホームスキルだったらわかるけど。）

```:CloudWatchより
2020-07-21T08:49:54.080Z	8299eb2c-xxxx-xxxx-xxxx-718f4315cc7f	INFO	~~~~ Error handled: TypeError: Cannot read property 'locale' of undefined
    at Object.getLocale (/var/task/node_modules/ask-sdk-core/dist/util/RequestEnvelopeUtils.js:26:36)
    at Object.process (/var/task/index.js:441:24)
    at GenericRequestDispatcher.<anonymous> (/var/task/node_modules/ask-sdk-runtime/dist/dispatcher/GenericRequestDispatcher.js:83:65)
    at step (/var/task/node_modules/ask-sdk-runtime/dist/dispatcher/GenericRequestDispatcher.js:45:23)
    at Object.next (/var/task/node_modules/ask-sdk-runtime/dist/dispatcher/GenericRequestDispatcher.js:26:53)
    at /var/task/node_modules/ask-sdk-runtime/dist/dispatcher/GenericRequestDispatcher.js:20:71
    at new Promise (<anonymous>)
    at __awaiter (/var/task/node_modules/ask-sdk-runtime/dist/dispatcher/GenericRequestDispatcher.js:16:12)
    at GenericRequestDispatcher.dispatch (/var/task/node_modules/ask-sdk-runtime/dist/dispatcher/GenericRequestDispatcher.js:71:16)
    at CustomSkill.<anonymous> (/var/task/node_modules/ask-sdk-core/dist/skill/CustomSkill.js:103:69)
```


#### スキルが受け取っていたRESPONSE

Alexaアプリからスキルのアクセス権限を設定すると、以下のレスポンスがスキルに送信され、スキルが実行されていた。

```json:RESPONSE
{
    "version": "1.0",
    "context": {
        "System": {
            "application": {
                "applicationId": "amzn1.ask.skill.a50371d8-xxxx-xxxx-xxxx-87397c58045d"
            },
            "user": {
                "userId": "amzn1.ask.account.xxxxxxxxxxxxxxxxxxxxxxxxxxx"
            },
            "apiEndpoint": "api.fe.amazonalexa.com"
        },
        "request": {
            "type": "Alexa.Authorization.Grant",
            "requestId": "ffdb65ad-xxxx-xxxx-xxxx-463df2c366eb",
            "timestamp": "2020-07-21T09:28:50Z",
            "body": {
                "grant": {
                    "type": "OAuth2.AuthorizationCode",
                    "code": "SRJCmcfWxxxxxxxxxxxx"
                }
            }
        }
    }
}
```


#### なぜ権限が保存されない？（推測）

少し前の `ASK CLI v2`では、 `ask new` すると、以下のような `i18next` を使用した、LocalisationRequestInterceptorがデフォルトで生成されていた。
（※現在は、テンプレートが変更されたようで、`ask new` してもこのようなコードは生成されない。）

```java:インターセプター（抜粋）
// This request interceptor will bind a translation function 't' to the handlerInput
// Additionally it will handle picking a random value if instead of a string it receives an array
const LocalisationRequestInterceptor = {
    process(handlerInput) {
        const localisationClient = i18n.init({
            lng: Alexa.getLocale(handlerInput.requestEnvelope),
            resources: languageStrings,
            returnObjects: true
        });
```
RESPONSEを受け取った後のスキルの動作としては、

1. 上記のRESPONSE（ `Alexa.Authorization.Grant` ）をスキルが受け取る。
2. インターセプター（ `LocalisationRequestInterceptor` ）が実行される。
3. `Alexa.getLocale` で `locale` が取得できずエラーが発生する。
4. `ErrorHandler` が実行されスキル終了。

RESPONSE（ `Alexa.Authorization.Grant` ）をスキルが受け取ったときに、スキルはハンドラーを特定する前に、インターセプター（ `LocalisationRequestInterceptor` ）を実行する。この時、インターセプター内で使用されている  `Alexa.getLocale` に渡している `requestEnvelope（上記のRESPONSE）` には `locale` が含まれていない。そのため `Alexa.getLocale` で `locale` が取得できずエラーが発生し、その影響でアクセス権限が保存される前にスキルが停止していたと推測している。

（※`lng: Alexa.getLocale(handlerInput.requestEnvelope),` の `Alexa.getLocale` で `Cannot read property 'locale' of undefined` が発生する。）

# 応急対策

#### 1. try - catch で回避

インターセプター内でエラーが発生しないようにする。`try - catch` で回避した。これでAlexaアプリから設定いたアクセス権限の情報が保存されるようになった。

```javascript:インターセプター（抜粋）
// This request interceptor will bind a translation function 't' to the handlerInput
// Additionally it will handle picking a random value if instead of a string it receives an array
const LocalisationRequestInterceptor = {
    process(handlerInput) {
        try {
            const localisationClient = i18n.init({
                lng: Alexa.getLocale(handlerInput.requestEnvelope),
                resources: languageStrings,
                returnObjects: true
            });
```

ただし、インターセプターは通過するようになったものの、ハンドラー（ `LaunchRequestHandler` ）処理では、`requestEnvelope（上記のRESPONSE）` を元にリクエストタイプを取得するのだが、`request.type` の階層が異なるため `Alexa.getRequestType` では、タイプを取得できず `TypeError: Cannot read property 'type' of undefined` エラーを引き起こす。しかし、ハンドラー内のエラーであれば、理由は不明だがアクセス権限は保存される。今のところはこれでアクセス権限の保存問題は回避できているので、いったん調査はストップ。

```json:インテントタイプ
        handlerInput.requestEnvelope.request.type === 'LaunchRequest';
```
```json:Alexa.Authorization.Grant
        handlerInput.requestEnvelope.context.request.type === 'Alexa.Authorization.Grant';
```

#### 2. i18next を使用しない

スキルの多言語化が必要ないのであれば、`i18next` をやめてしまう。
要するに、インターセプターで `requestEnvelope` を使用しないようにすることで、予想していないものを拾う可能性をなくす。

# 恒久対策

エラーとアクセス権限の保存の関係については、現在、Amazonさんが調査中。（2020年7月22日時点）
その結果がわかりしだい対策を考える。

# 参考
- [アクセス権限を設定してAlexaへのユーザー認証を実現する（※Amazon公式サイト）](https://developer.amazon.com/ja-JP/docs/alexa/smarthome/authenticate-a-customer-permissions.html)
- [Alexa.Authorizationインターフェース（※Amazon公式サイト）](https://developer.amazon.com/ja-JP/docs/alexa/device-apis/alexa-authorization.html)
- [AlexaタイマーAPIリファレンス（※Amazon公式サイト）](https://developer.amazon.com/ja-JP/docs/alexa/smapi/alexa-timers-api-reference.html)
- [ゆでたまごタイマー](https://amzn.to/3eU6uef)

#### 以上