PWAを体感できるサービス「ぱちぱち」をリリースした

今月のはじめあたりから個人で開発をしていたWebサービス「ぱちぱち」を **リリース** しました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">🎉「ぱちぱち」をリリースしました！🎉<br><br>相手に気軽にぱちぱちを送れます👏<br>プッシュ通知に対応していて、ぱちぱちを受け取ると通知がきます💌<br>アカウント登録してくれた方は出来る限り私からぱちぱちを送ります👏<br><br>是非登録してみんなに「ぱちぱち」を送ってみてください🙏<a href="https://t.co/0pQgdWOHqT">https://t.co/0pQgdWOHqT</a></p>&mdash; おりばー@技術書典5 【き27】 (@oliver_diary) <a href="https://twitter.com/oliver_diary/status/1046310385911910400?ref_src=twsrc%5Etfw">2018年9月30日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


元々は **技術書典5** の執筆を行う上で、扱っていたテーマが **PWA** ということもあり、そのサンプルアプリケーションとして作成していたのですが、ブラッシュアップをすれば世にリリースできるのでは？と思ったので**リリース**した感じになります。

是非アクセスして、利用していただければ幸いです。

また、技術書典5のサンプルアプリとしてプロトタイプ版のソースコードを**10/8**に公開する予定なので、お楽しみに！

【追記】
**プロトタイプ版のコードを公開しました**
https://github.com/minakawa-daiki/pachipachi.fun-gottani-bon

## リリースしたサービス
[相手に気軽に おめでとう や ありがとう の意味を込めて「ぱちぱち」を送れるサービス - ぱちぱち](https://pachipachi.fun)

<img width="1440" alt="image2.png" src="https://qiita-image-store.s3.amazonaws.com/0/213538/35a249b2-259b-733d-f86b-c0b84439a6bf.png">



### どんなサービスなのか
「ぱちぱち」では相手に感謝の意を込めて拍手を送ることができます。それだけです笑。
それだけのアプリなのですが、 **PWA** によって様々な機能を付けています。その一例が **Push通知** です。

まだ Safari には対応できていない（Safariが対応していない）のですが、デスクトップ版の Google Chrome や Firefox、アンドロイドなどには対応できており、WebからPush通知を受け取ることができます。

また、オフラインでも接続エラーに**ならない**ページやホーム画面にアプリとしてインストールできるので、是非このアプリで **PWA** の片鱗を体感して欲しいです。

### 「ぱちぱち」でできること
- Twitter で **ログイン** ができる
- ぱちぱちを **相手に送る** ことができる
- ぱちぱちを受信して **一覧を見る** ことができる
- ぱちぱちをしたことを **ツイート** できる
- **ホーム画面** に追加できる
- **オフライン** でも開けるページがある
- ぱちぱちしたことを相手に **Push通知** できる

### トップ画面
<img width="1440" alt="image1.png" src="https://qiita-image-store.s3.amazonaws.com/0/213538/007df1a4-c3be-6baa-799b-3d4cc1186ef9.png">

トップ画面からは自分の「ぱちぱち」リンクを共有できたり、受け取った「ぱちぱち」の一覧などが表示されます。

### ぱちぱち画面
<img width="1440" alt="image3.png" src="https://qiita-image-store.s3.amazonaws.com/0/213538/9e50de9d-f41f-0021-c2f6-d239443ede6f.png">

ぱちぱち画面からは相手に「ぱちぱち」を送ることができます。受け取った相手は、通知を許可してかつ **Push通知** に対応している端末であれば通知を受け取ることができます。

<img width="343" alt="Screen Shot 2018-09-30 at 8.25.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/213538/62bd4926-c696-fa44-0b42-c094d22a0115.png">

また、「ぱちぱち」したことを同時にツイートすることもできます。

## どうしてこのサービスを作ったのか
皆さんは「Yo」というアプリを覚えていますか？「Yo」はただ相手に「Yo」と送る**だけ**のアプリなのですが、自分にとってはかなり衝撃的なアプリでした。
何が衝撃的かというと、ここまでやっていることが**シンプル**なのに、世界中の人たちはメチャクチャ利用しているという点です。しかも実際に使ってみるとハマるという。

しかし、この「Yo」は意外とコミュニケーション能力が求められ、あまり日本人受けはしなかったと聞いています。

なので、今回この「Yo」というアプリを元に、 **PWA** という技術を用い、しかも「**ぱちぱち**」という日本人に馴染みのある拍手を相手に送ることで、楽しんでもらいたいという思いがあり、このサービスを作りました。

## こだわり
とにかく**簡単**に相手に「**ぱちぱち**」を送れること。
また、 **Push通知** により、すぐに「**ぱちぱち**」に気づける。操作性も気にかけて作りました。

<img width="343" alt="Screen Shot 2018-09-30 at 8.25.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/213538/62bd4926-c696-fa44-0b42-c094d22a0115.png">


通知に関しては、ワンボタンでオフにすることができ、ストレスなく、使用してもらえるように心がけています。

![step5.png](https://qiita-image-store.s3.amazonaws.com/0/213538/7b9d1479-efec-da54-3be9-17cf715fb07a.png)


デザイン能力皆無な私でも私なりに頑張って０からデザインし、チュートリアルもしっかりと用意してます。
<img width="1440" alt="Screen Shot 2018-09-30 at 8.27.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/213538/979973f1-9edc-31cf-8179-84a54a07bc7e.png">

## 技術など
主に使用した技術などは以下の通りです。

- Ruby on Rails
- PWA, Service Worker
    - Web Push
    - Offline Cache
    - A2HS(Add to Homescreen)
- Bulma（CSSフレームワーク [https://bulma.io](https://bulma.io) ）
- Heroku

特に今回はデザインも一人でやるということで、 **Bulma** には大変助けられました。**素人**でもある程度のデザインができるので、是非使ってみることをオススメします！

また今回、デプロイは Heroku にて行なっているのですが、非常に楽でした。以前リリースしたサービスの [Sphotz](https://sphotz.jp) ではAWSを用いて公開しているのですが、サービスの特性上、自動化してデプロイさせるまでのコードを組むのになかなか学習コストが高く、時間がかかっていました。

しかし、今回はそんなに規模は大きくないと思い、Herokuを採用した結果、とても爆速でデプロイできているので最高です。

### 通知について
今回 **Push通知** を実装するにあたって、 [https://github.com/zaru/webpush](https://github.com/zaru/webpush) を利用させていただきました。特に　[Rails4におけるブラウザプッシュ通知(Web Push Notifications)実装](https://qiita.com/shimx/items/d23bb45b34c89f879db3)の記事のおかげでとても簡単にPush通知を導入することができました。

以下に 「**ぱちぱち**」 にPush通知を導入した際のコードを記しておきます。（多少修正を加えてあります）なお、「**ぱちぱち**」 の**Push通知**は　VAPID（Voluntary Application Server Identification）を利用しています。

まずはじめに webpush Gem を Bundle します。

```rb
...
gem 'webpush'
...
```

```shell
bundle install
```

次に VAPID鍵ペア を作成します。

```shell
$ bundle exec rails c
Loading development environment (Rails 5.2.1)
irb(main):001:0> vapid_key = Webpush.generate_key
irb(main):001:0> puts vapid_key.public_key 
# ここに表示された公開鍵をコピーしておく
irb(main):001:0> puts vapid_key.private_key
# ここに表示された秘密鍵をコピーしておく
```

取得した公開鍵と秘密鍵を `.env` に追加しましょう。また、Push通知に必要なsubject用に `WEB_PUSH_VAPID_MAIL` も追加しておきましょう。

```..env
WEB_PUSH_VAPID_PUBLIC_KEY=<先ほど取得した公開鍵>
WEB_PUSH_VAPID_PRIVATE_KEY=<先ほど取得した秘密鍵>
WEB_PUSH_VAPID_MAIL=<連絡先>
```

上記で設定した鍵を元に、 **Push通知** を送信するRubyのコードを記述しましょう。

```rb
def web_push_to
    message = {
        title: 'タイトル！',
        body: "通知内容！",
        icon: ActionController::Base.helpers.asset_path('icons/icon-192x192.png'),
        tag: Time.now.to_i
    }

    # subscription は後に実装するクライアント側から取得出来る情報（今回はサンプルなので１件目に送るものとします）
    subscription = Subscription.first

    Webpush.payload_send(
        message: JSON.generate(message),
        endpoint: subscription.endpoint,
        p256dh: subscription.p256dh,
        auth: subscription.auth,
        vapid: {
            subject: "mailto:#{ENV['WEB_PUSH_VAPID_MAIL']}",
            public_key: ENV['WEB_PUSH_VAPID_PUBLIC_KEY'],
            private_key: ENV['WEB_PUSH_VAPID_PRIVATE_KEY'],
            expiration: 12 * 60 * 60
        }
    )
end
```

`web_push_to` メソッドでは、まず `message` を作成しています。これがユーザに届ける通知の内容になっていて、属性は以下の通りです。

- title: Push 通知のタイトル
- body: Push 通知の内容
- icon: Push 通知に使用するアイコン
- tag: 同じ通知を受け取らないようにするためのタグ

`tag` がポイントで、ここは省略できるのですが付与しておくと、同じ端末の同じブラウザに複数回同じ通知を送らないように、 `tag` で制御出来ます。

次にクライアント側の実装です。

```js
//serviceworker.js.erb

var CACHE_VERSION = 'v1';
var CACHE_NAME = CACHE_VERSION + ':sw-cache-';

// ServiceWorker のインストール
function onInstall(event) {
  console.log('[Serviceworker]', "Installing!", event);
  event.waitUntil(
    caches.open(CACHE_NAME).then(function prefill(cache) {
      return cache.addAll([
        '<%= asset_path "application.js" %>',
        '<%= asset_path "application.css" %>',
        '/offline.html',
      ]);
    })
  );
}

// ServiceWorker のアクティベート
function onActivate(event) {
  console.log('[Serviceworker]', "Activating!", event);
  event.waitUntil(
      caches.keys().then(function(cacheNames) {
        return Promise.all(
            cacheNames.filter(function(cacheName) {
              return cacheName.indexOf(CACHE_VERSION) !== 0;
            }).map(function(cacheName) {
              return caches.delete(cacheName);
            })
        );
      })
  );
}

function onFetch(event) {
   //省略🙏
}

// Push通知を受け取った時のイベントコールバック
function onPush(event) {
  console.log('[Serviceworker]', "Received push message!", event);
  // event.data にサーバー側で設定した項目が入っている
  var json = event.data.json();
  return event.waitUntil(
      self.registration.showNotification(json.title, {
        body: json.body,
        icon: json.icon,
        tag: json.tag,
        data: {
          target_url: '/'
        }
      })
  );
}

// 通知をクリックした時の動作
function onSWNotificationClick(event) {
  event.notification.close();
  return event.waitUntil(
      clients.openWindow(
          event.notification.data != null ? event.notification.data.target_url : '/'
      )
  );
}

// Push通知の Subscription が変わった時のイベントコールバック
function onPushSubscriptionChange(event) {
   //省略🙏
}

self.addEventListener('install', onInstall);
self.addEventListener('activate', onActivate);
self.addEventListener('fetch', onFetch);
self.addEventListener("push", onPush);
self.addEventListener("notificationclick", onSWNotificationClick);
self.addEventListener("pushsubscriptionchange", onPushSubscriptionChange);
```

```js
//serviceworker-companion.js.erb

//サーバ側の公開鍵を設定
var convertedVapidKey = new Uint8Array(
    <%= Base64.urlsafe_decode64(ENV['WEB_PUSH_VAPID_PUBLIC_KEY']).bytes %>
);

//　ServiceWorker の登録
if (navigator.serviceWorker) {
  navigator.serviceWorker.register('/serviceworker.js', { scope: './' })
      .then(function(reg) {
        swRegistration = reg;
        console.log('[Companion]', 'Service worker registered!');
        subscribe();
      });
}

//　Push通知の購読
function subscribe() {
  if (!canNotification()) { return; }
  var newSubscription = null;
  return swRegistration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: convertedVapidKey
  }).then(function (subscription) {
    newSubscription = subscription;
    console.log('User is subscribed.');
  }).catch(function(e) {
    console.error('Unable to subscribe to push.', e);
  }).then(function () {
    if (newSubscription !== null) {
      createSubscriptionOnServer(newSubscription);
    }
  });
}

//　Push通知が使用できるかの確認
function canNotification() {
  // When not supported push notification
  if (!('showNotification' in ServiceWorkerRegistration.prototype)) { return false; }
  // When deny push notification
  if (Notification.permission === 'denied') { return false; }
  // When not exist PushManager
  if (!('PushManager' in window)) { return false; }
  return true;
}

//　サーバ側にクライアントの subscription を送信
function createSubscriptionOnServer(subscription) {
  return fetch('<あなたが設定したRails側のsubscription登録エンドポイント>', {
    body: JSON.stringify({
      subscription: subscription
    }),
    headers: {
      'content-type': 'application/json'
    },
    method: 'POST'
  })
}

```

この設定でブラウザを開くと 通知 を受け取るかどうかを尋ねられ、許可をしておくと、サーバ側で**Push通知**を送信した際、クライアントに通知が届きます！




### ぱちぱちトップページのスコア
<img width="471" alt="image4-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/213538/edcc43d5-e717-f9b2-ce76-b0fdbdaba84c.png">


全部１００にしていきたいですね。

## 開発中に感じたこと
**PWA** をやる前は、 Service Worker を利用して実装していくんだろうなぁぐらいの感覚でしかなく、また、Service Worker の学習コスト高いんだろうなぁと思っており、なかなか手を付けられずにいませんでした。

しかし、今回のアプリを通して、PWAを用いたアプリの開発ができ、またその凄さを体感するとともに、早くリリースして、みんなにも味わってもらいたいと感じていました。

いつもなら思考停止で「**通知を許可しない**」を選んでしまう人でも今回は是非体験してしてほしいと思うのが私の思いです。

特に Android　ユーザの方はホーム画面に追加していただきたいです。

## 「ぱちぱち」のこれから
「ぱちぱち」を皆さんに利用していただき、さまざまな声をいただきたいです。**PWA**としての感想やアプリ自体の感想など。そうして皆さんと一緒に「ぱちぱち」をより良いサービスにしていきたいと思っています。

また、私自身が個人で開発するケースが結構多く、今後も続けていきますので、温かく見守ってくださるとありがたいです。

## 最後に
前回のサービスリリース（2018/05）からかなり時間が経ってしまい、久々のリリースとなってしまいました。気持ちとしては１ヶ月に１サービスみたいな心がけをしているのですが、、、頑張ります💪

また、技術書典5に出す本は今回リリースした「**ぱちぱち**」を題材にしており、 Ruby on Rails と PWA を用いて、プロトタイプ版「ぱちぱち」を作成する内容ですので、良かったら手に取ってみてみてください！（本を刷り過ぎてしまったのは秘密...)

ぱちぱち: [https://pachipachi.fun](https://pachipachi.fun)
私のぱちぱち: [https://pachipachi.fun/users/oliver_diary](https://pachipachi.fun/users/oliver_diary)
Twitter: [https://twitter.com/oliver_diary](https://twitter.com/oliver_diary)