Ruby
JavaScript
Rails
Web
PWA

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

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

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

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

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

リリースしたサービス

相手に気軽に おめでとう や ありがとう の意味を込めて「ぱちぱち」を送れるサービス - ぱちぱち

image2.png

どんなサービスなのか

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

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

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

「ぱちぱち」でできること

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

トップ画面

image1.png

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

ぱちぱち画面

image3.png

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

Screen Shot 2018-09-30 at 8.25.18.png

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

どうしてこのサービスを作ったのか

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

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

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

こだわり

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

Screen Shot 2018-09-30 at 8.25.18.png

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

step5.png

デザイン能力皆無な私でも私なりに頑張って0からデザインし、チュートリアルもしっかりと用意してます。
Screen Shot 2018-09-30 at 8.27.04.png

技術など

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

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

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

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

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

通知について

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

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

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

...
gem 'webpush'
...
bundle install

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

$ 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のコードを記述しましょう。

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

    # subscription は後に実装するクライアント側から取得出来る情報(今回はサンプルなので1件目に送るものとします)
    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 で制御出来ます。

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

//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);
//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通知を送信した際、クライアントに通知が届きます!

ぱちぱちトップページのスコア

image4-2.png

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

開発中に感じたこと

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

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

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

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

「ぱちぱち」のこれから

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

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

最後に

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

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

ぱちぱち: https://pachipachi.fun
私のぱちぱち: https://pachipachi.fun/users/oliver_diary
Twitter: https://twitter.com/oliver_diary