中安拓也のブログ

プログラミングについて書くブログ

【JavaScript】Promise チェーンで、戻り値がPromiseのメソッドをつなぐ

戻り値がPromiseのメソッドを順番に実行し、前の処理の結果を次の処理で使いたいという場合があります。そんな時は、Promise チェーンを使いましょう。

バージョン情報

  • Angular: 5.0.1
  • TypeScript: 2.4.2

サンプルコード

下記のコードは、iOSのFace ID/Touch ID認証に関するコードになります。呼び出しているメソッドの戻り値が全てPromiseなので、Promise チェーンで繋げることができます。

.thenのコールバック処理の中で呼びだすPromiseが戻り値のメソッドをreturnしているところがポイント

// 戻り値がPromiseのメソッド
this.keychainTouchId.isAvailable()
    .then((res: any) => {
        console.log(res);
        // 戻り値がPromiseのメソッド
        return this.keychainTouchId.has(BioAuthService.KEY_A);
    }).then((res: any) => {
        console.log(res);
        // 戻り値がPromiseのメソッド
        return this.keychainTouchId
            .verify(BioAuthService.KEYCHAIN_KEY, `ロックを解除してください`);
    }).then((res: any) => {
        console.log(res);
        this.password = res;
        // 戻り値がPromiseのメソッド
        return this.storage.get(BioAuthService.KEY_B);
    }).then((res: any) => {
        console.log(res);
        this.userId = res;
        const params = {
            loginId: this.userId,
            password: this.password,
            deviceToken: null
        };
        this.action.login(params);
    }).catch((error: any) => {
        // catchは一つでよい
        console.error(error);
    });

Promiseの処理を順番に実行していく方法として、ネストしていくやり方もありますが、ネストが深くなっていくとコードが読みづらくなるので、コールバック地獄という言葉に代表されるように、アンチパターンであるとされています。

参考サイト

Promiseを使う - JavaScript | MDN

【Angular】お互いにDIしあっている(循環参照)と発生するエラー「Uncaught Error: Can't resolve all parameters for XXXService: (?, [object Object], [object Object]).」

バージョン情報

  • Angular: 5.0.1

エラー内容

AサービスクラスにBサービスクラスをDI、BサービスクラスにAサービスクラスをDI みたいなこと(循環参照状態)をしていた結果、タイトルのエラーが発生

Uncaught Error: Can't resolve all parameters for XXXService: (?, [object Object], [object Object]).
    at syntaxError (webpack-internal:///306:684)
    at CompileMetadataResolver._getDependenciesMetadata (webpack-internal:///306:15764)
    at CompileMetadataResolver._getTypeMetadata (webpack-internal:///306:15599)
    at CompileMetadataResolver._getInjectableMetadata (webpack-internal:///306:15579)
    at CompileMetadataResolver.getProviderMetadata (webpack-internal:///306:15939)
    at eval (webpack-internal:///306:15850)
    at Array.forEach (<anonymous>)
    at CompileMetadataResolver._getProvidersMetadata (webpack-internal:///306:15810)
    at CompileMetadataResolver.getNgModuleMetadata (webpack-internal:///306:15378)
    at CompileMetadataResolver.getNgModuleSummary (webpack-internal:///306:15216)

サービスクラスをもう一つ追加して、循環参照を解消した

Aサービスクラスから、いくつかメソッドをCサービスクラスに分離して、
Bサービスクラスには、Aサービスクラスでなく、CサービスクラスをDIするようにした(循環参照を解消)結果、解決した。

参考サイト

Can't resolve all parameters for User: (?, [object Object]). · Issue #34 · ionic-team/ionic2-starter-aws · GitHub

iOSのFace ID, Touch IDを使用したログイン機能を設計する

業務でFace ID, Touch IDを使ったログイン機能を実装することになったので色々と調べた。

作成予定のシステム

  • Ionic(JavaScript, Angular)で作成されたハイブリットモバイルアプリケーション
  • サーバーサイドはJava(Spring)
  • 通常のログインには、メールアドレスとパスワードを使用
  • Face ID, Touch IDによる認証が成功した時には、メールアドレス/パスワード の入力をスキップした状態でログインできる

Face ID, Touch IDに対する誤解について

Face ID, Touch IDによる認証が成功すると、認証した人を特定できるユニークなIDを発行してくれるイメージを勝手に持っていたが、そんな機能はなかった。Face ID, Touch ID のAPIが提供する情報は、あくまで認証が「成功したかどうか」のみでIDを発行したりはしない。

処理詳細

過去に類似の実装経験がある人に話を聞いた結果、下記のイメージで実装する予定。

初回ログイン

  1. UDIDを発行して、iOSデバイスのキーチェーンに格納
  2. ユーザーがログイン画面にメールアドレスとパスワードを入力してログイン
  3. この時、発行したUDIDもサーバーに送る
  4. 「UDID」と「メールアドレス・パスワード」を関連付けてDBに保存
  5. これで次回以降は、Face ID, Touch IDによる認証が有効になる

Face ID, Touch IDによるログイン

  1. Face ID or Touch IDを実行して成功する
  2. iOSデバイスのキーチェーンからUDIDを取り出す
  3. UDID+固定文字列をHMAC-SHA256形式でハッシュ化
  4. ハッシュと現在時刻を連結させたものを、AES256で暗号化、暗号化した結果をBASE64Encodeするこれをシグネチャとする
  5. シグネチャをサーバーに送る
  6. サーバー側でBASE64Decode、AES復号化、送られてきた現在時刻から10分以上経過していないことを確認する
  7. ハッシュが一致することを確認する
  8. UDIDが正しく登録されているか確認する
  9. 認証処置完了、認証トークンを発行する

Appleのレビューガイドライン

Appleのレビューガイドラインで、Face IDについての記載があったので転載する。

https://developer.apple.com/jp/app-store/review/guidelines/

2.5.13 顔認証でアカウントを認証するアプリケーションには、ARKitやその他の顔認証テクノロジーではなく、必ずLocalAuthenticationを使用する必要があります。また、13歳未満のユーザーに対しては、必ず代替の認証方法を用意する必要があります。

考えた点・補足

  • 端末のKeychainに直接メールアドレスとパスワードを保管するのは避けた
  • シグネチャは、改ざん防止ではなく、認証のために利用している
  • 顔・指紋認証に3回失敗すると、通常ログインに切り替える〜みたいな処理は、API(ライブラリ)側で勝手にやってくれる

結局違う設計にしました。この記事参照

IonicでiOSの生体認証(Face ID, Touch ID)を扱う

モチベーション

業務でFace ID, Touch ID対応のiOSアプリを作成することになったので、触っておきたい。

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使っています

  • ionic-angular@3.9.2
  • Angular@5.2.10

cordova-plugin-touch-idをインストール

Ionicアプリ上で、Face ID / Touch IDの機能を使うには、cordova-plugin-touch-idのインストールが必要になる。

$ ionic cordova plugin add cordova-plugin-touch-id
$ npm install --save @ionic-native/touch-id

cordova-plugin-touch-idをインストールすることで、Face ID / Touch IDの両機能を利用することが可能になる。

AppModuleにプラグインを追加

  • TouchIDモジュールをインポート
  • providers: []配列にTouchIDモジュールを追加

src/app/app.module.ts:

・・・

import { TouchID } from '@ionic-native/touch-id';

@NgModule({
  ・・・
  providers: [
    ・・・
    TouchID,
    ・・・
  ]
})
export class AppModule {}

これで、環境構築は完了🍺🍺🍺

実装

import { Component } from "@angular/core";
import { TouchID } from "@ionic-native/touch-id";

@Component({
  selector: "page-home",
  template: `<ion-content padding>
              <button (click)="login()" ion-button>Login</button>
            </ion-content>`
})
export class HomePage {
  constructor(private touchId: TouchID) {}

  login() {
    // Touch ID, Face ID 対応端末かどうか確認する
    this.touchId
      .isAvailable()
      .then(
        res => console.log("TouchID is available!"),
        err => console.error("TouchID is not available", err)
      );

    // 確認ダイアログを出したあと、Touch ID, Face ID 認証を実行する
    this.touchId
      .verifyFingerprint("Scan your fingerprint please")
      .then(res => console.log("Ok", res), err => console.error("Error", err));
  }
}

動作確認

シミュレーター上での動作確認について。シミュレーターを起動して、Hardware -> Face ID -> Enrolled を選択することで、シミュレーター上でもFace ID, Touch IDの動作確認を実施することができる。

f:id:l08084:20180602190520p:plain

参考サイト

【メモ】Xcodeでエラー「Showing All Messages clang: error: linker command failed with exit code 1 (use -v to see invocation)」

Xcodeで実機ビルドをしたタイミングで、下記のエラーが発生した。

Showing All Messages
clang: error: linker command failed with exit code 1 (use -v to see invocation)

バージョン情報

  • Xcode: 9.3
  • cordova-plugin-touch-id: 3.3.1

原因

cordova-plugin-keychain-touch-idcordova-plugin-touch-idの両方を入れたことがおそらく原因だと思う。
cordova-plugin-keychain-touch-idのインストールをやめて、cordova-plugin-ios-keychainに変更したところ、解決した。

機能が一部、重複しているライブラリを入れたことが原因なのか....?

  • cordova-plugin-touch-id

    • Ionicアプリ上で、Face IDとTouch IDによる認証機能を呼び出すライブラリ
  • cordova-plugin-keychain-touch-id

    • Ionicアプリ上で、Face IDとTouch IDによる認証機能と、Keychainへのパスワード登録機能を可能にするライブラリ

iOS端末(iPhone/iPad)のUDIDを確認する方法

Apple開発者アカウントにデバイスを登録するときに必要になる、デバイスID(UDID)の確認方法について

バージョン情報

  • MacBook Pro(15-inch, 2017)
  • macOS High Sierra: 10.13.4
  • iTunes: 12.7.4
  • iPhone 8 plus(iOS 11.1.2)
  • Xcode: 9.3

UDIDの確認方法

デバイス番号(UDID)を調べる方法は、複数存在している。今回は、そのうちのiTunesを使う方法とXcodeを使う方法を紹介する

iTunesで調べる方法

この方法だと、iPhone(iPad)が強制的に同期されてしまうので、業務でテスト端末を使うときなどの場合は避けたほうがいいかもしれない

f:id:l08084:20180519164911p:plain

  1. Mac上でiTunesを起動する
  2. MacにiPhone(iPad)を接続する
  3. シリアル番号が表示されているところをクリック
  4. iPhone(iPad)のUDIDが表示される

Xcodeで調べる方法

f:id:l08084:20180519170208p:plain

  1. Mac上でXcodeを起動する
  2. 「Window」>「Devices」コマンドを実行
  3. iPhone(iPad)をMacに接続する
  4. Debicesタブを選択する
  5. identifierの部分にデバイス番号(UDID)が表示される

参考サイト

日本語ドキュメント - Apple Developer

【JavaScript】Ionicで架空のECアプリを作成する #10 - 完成

前回の記事はこちら

ハイブリットモバイルアプリフレームワークのIonicを使って、架空のアパレルショップにおける注文アプリを作成していました。

最低限の機能は、実装できたかな?というところまで来たので、今回で一旦完成ということにします。

下記のURLから実際にさわれます。

https://l08084.github.io/ionic-sample-shopping-app/www

後からリポジトリを見直した時になんのシステムかわかるように、README.mdを書く アンド GitHub Pagesにプッシュして完了にしたいと思います。

  • GitHubリポジトリ

github.com

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使っています

  • ionic-angular@3.9.2
  • Angular@5.2.10
  • Google Chrome バージョン: 60.0.3112.113

README.mdを書く

README.mdの構成ですが、Getting Started(ローカルでのアプリの動かし方)とApp Preview(各画面のスクリーンショット)だけ書いて終わりにします。

各画面のスクショをとる

$ ionic serve --labを使うと、各種OS(iOS, Android, Windows)の動作確認を一度に実行することができるので、便利です。

f:id:l08084:20180512192139p:plain
商品リスト画面のスクショ

f:id:l08084:20180512193014p:plain
商品詳細画面のスクショ

f:id:l08084:20180512193139p:plain
カート画面のスクショ

撮ったスクリーンショットをresources/screenshots/配下に配置して、README.mdから参照できるようにする。

README.md:

## App Preview

[Try it live](https://l08084.github.io/ionic-sample-shopping-app/www)

* Home Page

  <img src="resources/screenshots/HomePage.png">

* Detail Page

  <img src="resources/screenshots/DetailPage.png">

* Cart Page

  <img src="resources/screenshots/CartPage.png">

MacPCを使っていて、かつエディタがVSCodeのかたは、キーボードで「command + shift + v」と打つことで、マークダウンのプレビューをみることが出来るので、便利ですよ

GitHub Pagesに配置する

IonicアプリをGitHub Pagesで公開する - 中安拓也のブログ

IonicアプリをGitHub Pages上で公開する方法については、上記記事を参考にしてください。

本アプリについては、https://l08084.github.io/ionic-sample-shopping-app/www に公開しました。

以上