イベント通知アプリの作成(firebaseベース)

いろいろ試行錯誤しましたが、PWAでのPush通知はちょっと難しいと判断しfirebaseを使っての通知にすることとしました。

#動作結果
先に結果から
webにアクセスして通知を許可すると、こんな感じで通知されます。
![通知.png](https://qiita-image-store.s3.amazonaws.com/0/230735/6a7250db-40a6-764a-19ce-a51772aca062.png)

ページを閉じていても通知が届きます(Windows10)
画面右下にこんな感じで通知されます
![通知_画面右下.png](https://qiita-image-store.s3.amazonaws.com/0/230735/c738a768-c4b6-bf4d-078e-c2d4a2fa0db1.png)
通知リストにもこんな感じで出てきます
![通知_web画面開いていない時.png](https://qiita-image-store.s3.amazonaws.com/0/230735/bfbcceba-8e9e-cc69-a599-628ede716989.png)


#index.html
```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>xxxxxxxxxxx Scheduler</title>
    
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <link rel="manifest" href="/manifest.json">
    <!-- ios用tag -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="apple-mobile-web-app-title" content="PWA">
    <link rel="apple-touch-icon" href="./img/app-icon-120x120.png" sizes="120x120">
    <link rel="apple-touch-startup-image" href="./img/zeni_splash.png" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/siimple@3.1.1/dist/siimple.min.css" />
    <link href="https://fonts.googleapis.com/css?family=Poiret+One" rel="stylesheet">
    <link type="text/css" rel="stylesheet" href="./css/bigapple.css">
    
    <!-- update the version number as needed -->
    <script defer src="/__/firebase/5.8.3/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/5.8.3/firebase-auth.js"></script>
    <script defer src="/__/firebase/5.8.3/firebase-database.js"></script>
    <script defer src="/__/firebase/5.8.3/firebase-messaging.js"></script>
    <script defer src="/__/firebase/5.8.3/firebase-storage.js"></script>
    <!-- initialize the SDK after all desired features are loaded -->
    <script defer src="/__/firebase/init.js"></script>

    <script type="text/javascript">
      // service workerが有効なら、service-worker.js を登録します
      if ('serviceWorker' in navigator) {
        console.log("service-worker ON");
        navigator.serviceWorker.register('./firebase-messaging-sw.js').then(function() { console.log('Service Worker Registered'); });
     }
    </script>

  </head>
  <body>
    <!-- HEADER -->
  <div class="ens-title-area">
    <div class="ens-title">
      <img class="ens-logo" src="./img/xxxxxxxxxxxxxxx.png" alt="ロゴ"/>
      EVENT NOTICE SERVICE
    </div>
  </div>
  <!-- MAIN CONTENTS -->
  <div class="wrapper">
    <div class="page_title"><p><span>xxxxxxxxxxx Scheduler</span></p></div>
    <div class="bg_wrapper">
      <div class="contents_wrapper">
        <div id="canvas"></div>
        <div class="form_area">
          <div>
            <label for="event_title">イベント名：</label><input type="text" id="event_title">
          </div>
          <div>
            <label for="event_date">イベント日：</label><input type="date" id="event_date">
          </div>
        </div>
        <div id="btn_area" class="btn_area">
          <button class="enable_click" id="get_btn" onclick="">イベント取得</button>
          <button class="enable_click" id="write_btn" onclick="">イベント追加</button>
        </div>
      </div>
    </div>
    <!-- FOOTER -->
    <div class="ens-footer">Copycenter 2018 xxxxx Limited.</div>
  </div>
  <script src="./js/jquery.js"></script>
<script src="./js/firebase.js"></script>
```

```javascript:index.html
    <script>
        var config = {
    apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    authDomain: "xxxxxxxxxxxxxxxxxxxxxxx",
    databaseURL: "xxxxxxxxxxxxxxxxxxxxxxxxx",
    projectId: "xxxxxxxxxxxxxx",
    storageBucket: "xxxxxxxxxxxxxxxxxxxxxx",
    messagingSenderId: "xxxxxxxxxxxxxxxxxxx"
  };
  firebase.initializeApp(config);
  // firestoreインスタンスの生成
  var db = firebase.firestore();
  // タイムスタンプの設定を記述
	var setting = { timestampsInSnapshots:true };
	db.settings(setting);
  
  // メッセージングの初期化
  var messaging = firebase.messaging();
    // 画面表示時の通知
    messaging.onMessage(function(payload) {
    console.log("Message received. ", payload);
    alert(payload.notification.title+"\r\n"
        +payload.notification.body+"\r\n"
        +payload.notification.icon);
  });
$(function(){

//プッシュ通知パーミッション取得
  messaging.requestPermission()
  .then(function() {
    //ユーザー毎のトークンを取得して画面に表示する
    messaging.getToken()
    .then(function(token) {
      console.log('Token refreshed.');
      console.log(token);
      db.collection("tokens").add({
        token: token
      });
      document.getElementById("txtIIToken").value = token;
    })
    .catch(function(err) {
      console.log('Unable to retrieve refreshed token ', err);
    });
  })
  .catch(function(err) {
    console.log('Unable to get permission to notify.', err);
  });
  
  $("#write_btn").bind("click touchstart", function(){
    // フォームから値を取得
		var event_title = document.getElementById("event_title").value;
		var event_date  = document.getElementById("event_date").value;
		event_date = modEventDate(event_date);
		
		// 日付を取得
		var now = getNowDate();
		
		// イベント追加
		addEvent(db, "events", event_title, event_date, now);
		
		// フォームの初期化
		document.getElementById("event_title").value = "";
		document.getElementById("event_date").value = "";
    db.collection("tokens").get().then(function(querySnapshot) {
			querySnapshot.forEach(function(doc) {
        console.log(doc.get("token"));
        // イベント通知
        $.ajax({
          url:'https://fcm.googleapis.com/fcm/send',
          type:"POST",
          headers:{
            "Authorization":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "Content-Type":"application/json"
          },
          contentType: 'application/json',
          dataType: "json",
          data:JSON.stringify({
            notification: {
              title: "イベント通知", // 通知タイトル
              body: "新しいイベントが追加されました。", // 通知内容
              click_action: "https://xxxxxxxxxxxxxx.firebaseapp.com/", // クリック時の動き
              icon: "./img/icon.png" // 通知アイコン
            },
            to: doc.get("token") // 送信対象
          }),
          success:function(json) {
            console.log("success");
            console.log(json);
          },
          error: function() {},
          complete: function() {}
        });
				
      });
    });

    
  });

  // イベント取得クリック時
  $("#get_btn").bind("click touchstart", function(){
    // 初期化
    document.getElementById("canvas").innerHTML = ""
		
		// イベント全件取得
		db.collection("events").get().then(function(querySnapshot) {
			querySnapshot.forEach(function(doc) {
				document.getElementById("canvas").innerHTML += 
					doc.get('event_date');;
				document.getElementById("canvas").innerHTML +=
					doc.get('event_name'); + " <br /> ";
				document.getElementById("canvas").innerHTML += "<hr />";
			});
		});
  });

});

// イベント追加関数
function addEvent(db, doc, name, date, now){
	db.collection(doc).add({
		event_name: name,
		event_date: date,
	});
}
// 日付フォーマット変更処理
function modEventDate(date){
	var result = date.replace("-", "/");
	// "-"が存在する限り繰り返し
	while(result !== date) {
		date = date.replace("-", "/");
		result = result.replace("-", "/");
	}
	//TODO:スマホからだとyyyymdになるためyyyymmddに変換
	//     無理矢理だが今は動けば良いので機会があれば修正する。
	var tmpDate
	tmpDate = new Date(date);
	var yyyy = tmpDate.getFullYear();
	var mm   = ("00" + (tmpDate.getMonth()+1)).slice(-2);
	var dd   = ("00" + tmpDate.getDate()).slice(-2);
	var res  = yyyy + "/" + mm + "/" + dd;
	return res;
}

// 現在時刻の取得関数
function getNowDate(){
	var date = new Date();
	var yyyy = date.getFullYear();
	var mm   = ("00" + (date.getMonth()+1)).slice(-2);
	var dd   = ("00" + date.getDate()).slice(-2);
	var res  = yyyy + "/" + mm + "/" + dd;
	return res;
}
  </script>

```

#firebase-messaging-sw.js

```javascript:firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/3.7.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.7.0/firebase-messaging.js');

// service-worker.js
self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');
});

self.addEventListener('activate', function(e) {
  console.log('[ServiceWorker] Activate');
});


// 現状では、この処理を書かないとService Workerが有効と判定されないようです
self.addEventListener('fetch', function(event) {});


firebase.initializeApp({
    'messagingSenderId': 'xxxxxxxxxxxxxx'
});

// メッセージングの初期化
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
  // Customize notification here
  const notificationTitle = payload.notification.title;
  const notificationOptions = {
      body: payload.notification.body,
      icon: payload.notification.icon
    };
  return self.registration.showNotification(notificationTitle,notificationOptions);
});
```

#manifest.json

```json:manifest.json
{
  "name": "PWA Sample",
  "short_name": "PWA",
  "background_color": "#fc980c",
  "icons": [
    {
      "src": "./img/BIG_APPLE.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "./img/BIG_APPLE_144.png",
      "sizes": "144x144",
      "type": "image/png"
    }
    ],
  "start_url": "https://xxxxxxxxxxxx.firebaseapp.com/",
  "display": "standalone",
  "theme_color": "#2F3BA2",
  "gcm_sender_id": "xxxxxxxxxxxxx" // firebase送信者は固定
}

```