yasutomogのブログ

JavaScriptとAzureとPHPのメモ帳

OAuth2対応したPWAをiOSでホーム画面に追加すると認証が厳しい

概要

  • 社内アプリをPWAで構築したときのはなし
  • 社内の諸々はGoogleアカウントで管理しているので、認証はGoogleのOAuth2を使用
  • PWAはNuxt.jsを使って作成し、APIPHPのLaravel(社内サイトがLaravelを使ってたので流用)を使って作成
  • Google OAuth 2.0 認証を使用

結論(感想)

  • Nuxt.jsを使うことで、単純なPWA構築はSPAの開発経験者であればいける(キャッシュ機構、バックグラウンド実行、オンラインとオフラインの切替時の制御とか、この辺のナレッジを溜めることでPWA経験者としての優位性が出てくると思う)
  • OAuth2を使用する場合、認証画面を開くときに単純なリダイレクト(ブラウザの別タブで表示される)にするよりかは、window.openで認証画面を開き、認証成功時にwindowを自動で閉じるような流れにするほうが、UI/UX的にいい感じな気がする
  • OAuth2の認証を使用すると、Androidでは想像通りの動きでいける。
    iOSではSafariで動かしているときにはAndroidと同様で問題ないが、ホーム画面に追加してPWAアプリとして起動するとOAuth2の認証が以下の理由で厳しい。
    • 認証画面の表示で、別ドメインのリダイレクトが必要なる
    • PWAから別ドメインを開くときは、window.openなどを使用してもSafariアプリのタブで開かれる
    • Safariアプリがアクティブ(PWAが非アクティブ)のときは、PWAでバックグラウンドの実行などはできないように制御されている
    • PWAをアクティブにしたときは、状態は破棄されてアプリ再起動と同様の動きになる
    • CookieやLocalStrageはPWAとSafariで同一ドメインでも共有できない(Androidだとこの辺はいける)
  • 以下のfirebaseのGitHubでも同様のissueが上がっているので、もう少し様子見していこうと思う github.com
  • 何か迂回策とかあれば教えてほしいです!

Google OAuth2.0のざっくりとしたフロー

  • PHPでログイン状態を確認して未ログイン状態であれば、Googleアカウントのログイン画面へリダイレクト
  • Googleアカウントで認証後、コールバックURLとして設定した自分のサイトへリダイレクトされる
  • 自分のサイト内で、認証情報などを取得して、DBなりCookieなりに保存する

Androidの場合(ケース1)

  • 「ホーム画面に追加」したPWAの処理について
  • PWAで用意した認証ボタンをクリックしたときに、API側でGoogle認証画面へリダイレクト
  • PWAとは別に、通常のChromeブラウザで別タブが開かれて、Google認証画面が表示される
  • Chromeブラウザ上で認証したときに、Cookieに認証情報登録
  • PWAに戻ると、(同一ドメインで構築の場合)Chromeで登録した認証情報にアクセスできるので、それを使い認証が必要な機能の使用が可能となる

ケース1の簡易フロー
ケース1の簡易フロー

Androidの場合(ケース2)

  • Googleの認証画面へリダイレクトするAPI呼び出しをするときに、window.openを使用する
  • PWA上でwindowがフルサイズで開き、Google認証画面が表示される
  • ケース1とほぼ同じで、認証後はその情報を使い認証が必要な機能の使用が可能となる
  • ケース1よりも良い点としては、認証後のタイミングをpostMessageなどを使用することでイベント検知できるので、認証完了したときにwindowを自動で閉じて、認証後の初期ページに遷移など制御をすることが可能

ケース2の簡易フロー
ケース2の簡易フロー

iOS safariの場合

  • 「 ホーム画面に追加」したPWAを使用して検証
  • PWAからOAuth2を使用するためにGoogle認証画面にリダイレクトすると、iOS safariの別タブとして起動される(window.openを使用してもiOS safariが起動される)
  • Androidでは、PWAとChromeブラウザでは、同一ドメインCookieであればお互い参照することが可能だったが、iOSのPWAとsafariではお互いのCookieにはアクセスできないような制御となっている
  • iOSのPWAでは、アプリが起動中でもメインで動いていない場合には、バックグラウンドでの実行ができないため、iOS safariからpostMessageを使って認証後情報を連携しようとしてもできない
  • 上記理由から、現時点ではiOSでOAuth2を使いPWAの構築は厳しそう
  • もちろん、ホーム画面に追加せず、そのままsafari上ではOAuth2を使った制御も可能なため、解決方法が見つかるまではそれで進めたい

ケース3の簡易フロー
ケース3の簡易フロー

PHPでGuzzleHttpを使用して並列処理(同時実行数の制御)の確認

概要

  • PHPでHTTP通信の並列処理をする
  • curl_multiではなくGuzzleHttpを使って試す
  • GuzzleHttpは並列実行が元々用意されている

知りたかったこと

  • GuzzleHttpでPoolの使用時に設定するconcurrency(並列処理数)の制御
  • 具体的には、全部で10リクエスト投げる処理でconcurrencyに2を設定して実行した場合、以下のどちらになるか調査
    • 片一方のリクエストが先に返ってきても、もう片方のリクエストが返るまで待ち、常に2リクエストずつ処理する
    • レスポンスが返ってきて空いたところから次のリクエストを投げて常時2リクエストを保とうとする

試した環境

  • lumen: v5.5.2
  • GuzzleHttp: 6.3.3(lumen導入時に一緒に入ったもの)

試したサンプルコード

routes/web.php

  • リクエストパラメータの秒数分Sleep後にレスポンスを返す関数とURLのマッピング
$router->get('/wait', 'SampleController@wait');

app/Http/Controllers/SampleController.php

  • リクエストパラメータの秒数分Sleep後にレスポンスを返す関数
public function wait(Request $request)
{

    $wait = $request->input("sec");
    sleep((int)$wait);
    return "ok" . $wait;

}

app/Console/Kernel.phpの$commandsに追加

  • API呼び出しをartisanを呼び出すため、実行クラスを追加
    protected $commands = [
        \App\Console\Commands\Sample\ApiCaller::class,
    ];

app/Console/Commands/Sample/ApiCaller.php

  • APIを呼び出す実行クラス
  • 全部で10リクエスト呼び出す
  • 10リクエストの内、2つめ(indexが1)のリクエストのみ10秒でそれ以外は1秒で返されるようにしている
<?php

namespace App\Console\Commands\Sample;

use Illuminate\Console\Command;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;

class ApiCaller extends Command
{

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'apicall';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'API呼び出し処理';

    /**
     * API呼び出し処理
     */
    public function handle()
    {

        $this->multiReq();

    }

    private function multiReq()
    {

        $uri = [
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=10',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
        ];

        $client = new Client();
        $requests = function () use ($uri) {
            foreach ($uri as $u) {
                yield new Request('GET', $u);
            }
        };

        $pool = new Pool($client, $requests(), [
            'concurrency' => 2,
            'fulfilled' => function ($response, $index) {
                // this is delivered each successful response
                \Log::info("成功::index::" . $index);
            },
            'rejected' => function ($reason, $index) {
                // this is delivered each failed request
                \Log::info("失敗::index::" . $index);
            },
        ]);

        // Initiate the transfers and create a promise
        $promise = $pool->promise();

        // Force the pool of requests to complete.
        $promise->wait();

    }

}

実行コマンド

php artisan apicall

結果

  • 以下のようなログが出力される
  • 2つ目(indexが1)の10秒掛かるリクエストが返されるまでに3つ目のリクエストが投げられなか確認したところ、レスポンス待たずにどんどんリクエストしているので並列処理としては常時2つを保つように動いていることがわかった
[2018-12-05 17:17:34] lumen.INFO: 成功::index::0 [] []
[2018-12-05 17:17:36] lumen.INFO: 成功::index::2 [] []
[2018-12-05 17:17:38] lumen.INFO: 成功::index::3 [] []
[2018-12-05 17:17:40] lumen.INFO: 成功::index::4 [] []
[2018-12-05 17:17:42] lumen.INFO: 成功::index::5 [] []
[2018-12-05 17:17:43] lumen.INFO: 成功::index::1 [] []
[2018-12-05 17:17:44] lumen.INFO: 成功::index::7 [] []
[2018-12-05 17:17:44] lumen.INFO: 成功::index::6 [] []
[2018-12-05 17:17:45] lumen.INFO: 成功::index::8 [] []
[2018-12-05 17:17:45] lumen.INFO: 成功::index::9 [] []

Azure Data Studioの設定方法(SQLの実行計画)

概要

  • Azure SQL Databaseを使っていて、Azure Data Studio(MSが提供しているクライアントツール)を試してみた
  • 実行計画のところだけ、ハマったのでメモ

バージョン

  • OS:10.12.6
  • Azure Data Stduio:1.1.3

インストール

以下のサイトからダウンロードしてインストールするだけ!

docs.microsoft.com

DB接続

既にAzure SQL Databaseが用意されていれば、以下のサイトを参考にして接続情報を入力するだけ!

docs.microsoft.com

Widget追加(ダッシュボード)

  • これまで、Azure Portal上で確認していた遅いクエリなどをクライアントツールからも確認可能となる
  • 設定しておいて損はないと思うので、以下のサイトを参考にして設定するだけ!

docs.microsoft.com

実行計画

  • 参考にするサイトは、Widget追加と同じ
  • 参考サイトだと、実行計画の確認するときに「Explain」ボタンがクリックするようになっているが、デフォルトだと「Explain」ボタンが表示されていない
  • 「Explain」ボタンはデフォルト非表示になっているで以下の流れで表示するように設定する

    • Azure Data Studioを起動しDBに接続する
    • 「Command + Shift + P」でコマンドパレットを開く
    • コマンドパレットから「Preferences: Open User Settings」を開く
    • ユーザー設定画面で、「workbench.enablePreviewFeatures」を検索
    • 左側の鉛筆アイコンをクリックし、「false」から「true」へ変更
    • trueを設定すると、以下のスクショのように右側に反映される

    f:id:yasug:20181031103225p:plain

  • Command + Nとかでクエリのページを新規で開くと、「Explain」ボタンが表示されている

Nuxt.jsの開発サーバでHot Module Reloading (HMR)のエラー対策

概要

  • Nuxt.jsのテンプレートを使ったプロジェクト
  • 「yarn run dev」コマンドで開発サーバ起動(デフォルトの3000ポート)
  • コンソールログに「hot-update.json timed out」というエラー出力
  • ホットリローディングができない

バージョン

  • node:10.12.0
  • nuxt.js:2.0.0
  • nuxtjs/pwa:2.6.0
  • vue-cli:2.9.6
  • yarn:1.10.1

対応方法

  • 開発サーバのポートを変更
  • package.jsonのscriptsを変更
"scripts": {
  "dev": "PORT=3333 nuxt",
},
  • 特に3000番ポートは使用していなかったので、なぜこれでうまくいくようになったかは不明。。。

参考サイト(開発サーバのポート変更)

ja.nuxtjs.org

Nuxt.jsでPWAをGitHub Pagesにコマンドでデプロイする流れ

概要

  • Nuxt.jsのテンプレートとPWAモジュールを使用して作成したアプリをGitHub Pagesにコマンドラインでデプロイする方法のまとめ

バージョン

  • node:10.12.0
  • nuxt.js:2.0.0
  • nuxtjs/pwa:2.6.0
  • push-dir:0.4.1
  • vue-cli:2.9.6
  • yarn:1.10.1

参考記事

https://pwa.nuxtjs.org/setup

ja.nuxtjs.org

方法

Nuxt.jsでプロジェクト生成

vue init nuxt-community/starter-template nuxt-pwa

PWAモジュールを追加

  • プロジェクトのルートディレクトリでpwaモジュールを追加
yarn add @nuxtjs/pwa
  • nuxt.config.jsに「modules」と「manifest」を追記
modules: [
  '@nuxtjs/pwa',
],
manifest: {
  name: 'yasutomog nuxt pwa',
  lang: 'ja'
},
  • .gitignoreに追記
sw.*

GitHubの準備

  • 本プロジェクト用のリポジトリ作成
  • masterブランチに生成したファイルとディレクトリをaddしてPush
  • gh-pagesブランチを作成

push-dirを追加

yarn add push-dir
  • nuxt.config.jsに「routerBase」を追記
const routerBase = process.env.DEPLOY_ENV === 'GH_PAGES' ? {
  router: {
    base: '/nuxt-pwa/'
  }
} : {}

module.exports = {
  ...routerBase,
  • package.jsonのscriptsにGitHub Pages用のdeployとgenerateとbuildコマンドを追記
"scripts": {
  "deploy": "push-dir --dir=dist --branch=gh-pages --cleanup",
  "build:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt build",
  "generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt generate"
},

PWAの生成とデプロイ

yarn run generate:gh-pages
yarn run deploy

GitHub Pagesを確認

GitHub Pagesでアンダーバーのディレクトリ対応

概要

  • Nuxt.jsで構築したサイト(yarn run generateで生成したdist直下)をGitHub Pagesに上げると、_nuxtディレクトリ内のjsファイルなどが404になる
  • GitHub PagesではJekyllで処理されるため、アンダーバー付きディレクトリなどが正常に読み込まれない

対応方法

  • .nojekyllファイルをルートディレクトリに作成
    • touch .nojekyll

AjaxのCSVダウンロード(Excelでの文字化け対応)

概要

  • 既に色々なところであがっているトピックなので今更だが、Excelの特定バージョンでのみ文字化けするということがあったので、対策をメモ
  • 一般的にCSVExcelで開く時の文字化け対策としては以下2つ。
  • 今回はUTF-8(BOM付き)で対応していたところ、以下のExcelバージョンでのみ日本語が文字化け(同じ2007でも違うバージョンだと問題なく表示できていた、、)
    • Excel 2007(12.0.4518.1014)
  • ダウンロード処理のざっくりとした流れは以下。
    • ajaxでPOST通信
    • サーバ側からファイル内容を返す
    • JavaScriptでBlobを使いファイルとそのリンクを作成
    • aタグを作ってリンクを紐づけ、clickイベント実行

対応

  • JavaScriptShift_JISに変換してファイルを作成するように変更することで、文字化けしていたExcelでも正しく表示できました。
  • ファイル内容の文字コードを変更するために、encoding.js(1.0.29)を追加しました。

    github.com

// ダウンロード用Ajax通信のコールバック(サンプルコード)

let rt = response.responseText,
  strRt = Encoding.stringToCode(rt),
  arrRt = Encoding.convert(strRt, "SJIS", "UNICODE"),
  u8a = new Uint8Array(arrRt),
  blob = new Blob([u8a], { 'type' : 'text/csv;charset=sjis;' }),
  blobUrl = window.URL.createObjectURL(blob),
  a = document.createElement('a');

a.href = blobUrl;
a.download = 'hoge.csv';
a.click();