iOSとAndroidでPWAをサポート

*この記事は韓国語から翻訳したものです。不十分な部分があれば、いつでもフィードバックをいただければありがたいです！ ([オリジナル記事](https://blog.chungjungsoo.dev/dev-posts/pwa-support/), 同じく私が作成しました。)*

今回、[バスハニャーンサービス](https://hybus.app)をリニューアルしながら、PWAをより良くサポートするための努力を書いてみたいと思います。

## PWAとは？

PWAとはProgressive Web Appの略です。

Web環境でのサービスは比較的発見されやすく、他の人にリンク一つで簡単に共有できる特徴があります。

一方、Nativeアプリのような場合には、システムにインストールする方式なので、ユーザーエクスペリエンスを最大化することができ、アプリドロワーからアプリアイコンをクリックして簡単にアクセスできるという特徴がある。また、Nativeはオフラインでも使用が可能です。

PWAはNativeのこのような長所をWeb環境でも同様にサポートすることができます。

バスハニャーンは学校のシャトルバスの時刻表を簡単に表示するサービスで、ユーザーがより簡単にアクセスできるWebでサービスを作ることになり、毎回サイトを探して直接アクセスする手間を減らすためにPWAをサポートすることになりました。

PWAの詳しい情報は[こちら](https://web.dev/what-are-pwas/)をご覧ください。

## PWAのサポート条件

WebサービスがPWAをサポートするためには次のような条件が必要です。

* Web manifestファイル
* HTTPSでサービスが提供されること
* Appとして表示される時のアイコン
* Service Worker

### Manifest

ManifestファイルはPWAをインストールするための情報が含まれているファイルです。ファイルにはPWAの名前、PWAの説明、テーマの色、PWAのメイン画面のURL、アイコンのサイズとパスなどが含まれています。


```json
{
    "name": "버스하냥",
    "short_name": "버스하냥",
    "icons": [
        {
            "src": "icons/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "icons/android-chrome-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ],
    "theme_color": "#ffffff",
    "background_color": "#ffffff",
    "start_url": "/",
    "display": "standalone"
}
```

Keyの値を見ると、どのような役割をする値が含まれているか分かります。しかし、今回のバスハニャーンサービスは `Vite` を使って製作され、 `Vite` でマニフェストファイルを自動で生成してくれる `vite-plugin-pwa` プラグインを使って製作しました。

```typescript
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    VitePWA({
      // PWA setting
      manifest: {
        theme_color: '#ffffff',
        background_color: '#ffffff',
        display: 'standalone',
        scope: '/',
        start_url: '/',
        name: '버스하냥',
        short_name: '버스하냥',
        icons: [
          {
            src: 'image/pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png',
            purpose: 'any',
          },
          {
            src: 'image/pwa-maskable-192x192.png',
            sizes: '192x192',
            type: 'image/png',
            purpose: 'maskable',
          },
          {
            src: 'image/pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'any',
          },
          {
            src: 'image/pwa-maskable-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable',
          },
        ],
      },
    }),
  ],
})
```

manifestファイルを手動で作るのと入れる値には大きな違いはありません。しかし、手動で作る場合、ビルドする過程で別途にmanifestファイルをビルドrootディレクトリへ入れて配布する過程が必要ですが、プラグインを使うとその過程が一つ減るという違いがあります。

### HTTPS

HTTPSはHTTPプロトコルでTLS/SSLを利用した暗号化と検証が追加されたプロトコルです。 ただ、HTTPを使うと第三者がどのようなデータが送受信され、どのサイトにアクセスしたかを知ることができ、データを途中で修正して送ることもできるので(MITM Attack)、セキュリティーに脆弱です。

バスハニャーンは大規模なサービスではないし、メンテナンスをする人数が多くないので、証明書の更新や発行を気にしなくていいサービスを利用しようとしました。従来は、GCP App Engineにカスタムドメインを接続してサービスを展開していたが、トラフィックが多くなり、Free Tierが使えなくなり、サーバー費用が継続的に発生していた。今回、これを補完するため、Cloudflareが新たに開始したサービスである[Cloudflare Pages](https://pages.cloudflare.com/)を利用しました。このサービスの最大のメリットは、無料ティアでもサイト、リクエスト数、帯域幅が無制限ということです。 また、サーバーがCloudflare側にあれば、Cloudflareの[加護](https://blog.cloudflare.com/cloudflare-blocks-an-almost-2-tbps-multi-vector-ddos-attack/)も受けることができます。😆😆

### Icon

アイコンは、既存のPWAアプリをインストールした場合、アプリドロワーに表示されるアプリアイコンです。このアイコンは、既存のサービスと同じように対外協力処の承認のもと、学校のマスコットキャラクターであるハニャンイの応用動作を利用して製作しました。

### Service Worker

Service WorkerはPWAとブラウザ間のプロキシサーバーのような役割をします。オフラインのサポート、プッシュ通知のサポート、ウェブページのファイルキャッシュをすべてここで担当します。

Service Workerは今後のUX最適化記事で詳しく説明する予定ですが、簡単に説明すると、Manifestと同じように`Vite`の`vite-plugin-pwa`で設定が可能です。ビルド時、Service WorkerのJavaScriptファイルを自動で生成して、ディレクトリに自動で入ります。




## ダークモードとトップバー

今回のリニューアルサービスで一番大きく変わった点の一つです。既存のPWAでもダークモードをサポートしましたが、PWAとウェブでトップバーの色を動的に変更するAPIはありません。そのため、ダークモードとライトモードを使うと色の違いがあり、違和感がありました。


![bushanyang_darkmode.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/2e173fe7-80ec-d923-252f-c52fb4f7faab.gif)


どこも公式にサポートしていない機能だったので、自分で実装することにしました。

まず、トップバーの色をどのようにサポートするかを確認しました。


![meta_theme_color.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/95200d2b-5585-f11b-71eb-ab1937529cf2.png)


Meta tagで`theme-color`の値を指定すると、上部バーや周辺の色を変えてくれるそうです。[(リンク)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color)

サイトの読み込み時 `<head>` タグでスクリプトを実行してダークモードかライトモードか把握した後、そのモードに合わせて `theme-color` の値を変更する方法を試しました。

```javascript
let pageTheme = window.localStorage.getItem('theme')

if (pageTheme !== null) {
  const barStyle = document.querySelector('meta[name=theme-color]')

  if (barStyle !== null) {
    if (pageTheme === 'light') {
      barStyle.setAttribute('content', '#FFFFFF')
      
      // For iOS
      document.body.className = ''
      document.body.style.backgroundColor = '#FFFFFF'
    }

    if (pageTheme === 'dark') {
      barStyle.setAttribute('content', '#27272A')
      
      // For iOS
      document.body.className = 'dark'
      document.body.style.backgroundColor = '#27272A'
    }
  }
}
```

このスクリプトを入れてPWAを実行した結果、ダーク/ライトモードを変更すると、上部バーの色も変わることが確認できました！iOSでもテストした結果、正常に動作しました。


![dynamic-status-bar.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/f6986995-44b0-8eb4-f16b-5302863f7e63.gif)


この機能を適用したら、サービスのUI的な部分が改善され、ユーザーにより良いサービスを提供できるようになりました。次の記事では、バスハニャーンサービスのUXをどのように改善したかについて説明したいと思います。