PWA 푸시 알람 firebase 간단예제 [2편]


파베글을 썼는데 정말 어떻게 내가 한게 맞나싶을정도로 기억이 1도 안난다. ( 이렇게 단기기억만 갖고 살았던거니..? 무서울정도로 기억이 하나도 안난다) 그래서 느꼈다. 이건 무조건 정말 친절하게 글을 남겨둬야겠구나. 난 정말 대강써놓은건 봐도 못알아듣는구나… 친절한 나님이 되어야지!!

우선 오늘 웹앱에서 노티가 오게하는법(유저가 알림을 허용했는지 아닌지 확인, 맞다면 버튼 비활성화, 아니라면 버튼활성화 .. 뭐 이런것들!)은 완성했는데,

지금 이 글은 파이어베이스(이하 파베)를 이용하여, 토큰값을 받아서 푸시알림을 받는 과정을 적은 글이다..! 진짜 진심으로 미친듯이 친절하게 써줄테다. 미래의 나를 위해…

우선 실습을 또 해보자. 백날 설명해봐야 실습한번에 미치지못한다. (암암, 개발은 삽질이지!!)

우선 파베 깃허브에 들어가서 클론을 하거나 다운을 받자.

그리고 영상에서 는 로컬로 띄워서 실습을 하셨길래 나도 로컬로 띄웠다. (개인적으로 서버를 가지고있기때문에 로컬로 안띄워도 되는데 띄운이유는 서버에서 띄웠는데도 아무것도 안떴기 때문이다) 아래 사진마냥

firebase

빈칸으로 뜨길래 로컬로 안띄워서 그런건가 싶어서 로컬로 띄운건데 똑같음. 로컬도 아무것도 안뜬다.

(참고. 로컬로 띄우는 법.

npm install http-server -g

npm을 설치한뒤에

http-server

을 누르면 로컬주소가 나오고, 그 로컬주소를 치고들어가면 된다.

)

어쨋거나 그래서 그냥 로컬로 진행하기로 함. 로컬에서 i가 있는 원(url주소창 좌측에 있는)을 클릭하면 알림을 허용할건지 말건지에 대해서 나오는데 허용을 누르고 새로고침을 하면 갑자기 Instance ID Token이라며 값이 나온다. 그것이 키값이다..! 토근값!!

받은 코드값이 뭔가 좀 다른거같기는 한데,

index.html의 코드를 다시 쓰면

<!DOCTYPE html>
<!--
Copyright (c) 2016 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
  <meta charset=utf-8 />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Firebase Cloud Messaging Example</title>

  <!-- Material Design Theming -->
  <link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.orange-indigo.min.css">
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
  <script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>

  <link rel="stylesheet" href="main.css">

  <link rel="manifest" href="/manifest.json">
</head>
<body>
<div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">

  <!-- Header section containing title -->
  <header class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700">
    <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
      <div class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--8-col-desktop">
        <h3>Firebase Cloud Messaging</h3>
      </div>
    </div>
  </header>

  <main class="mdl-layout__content mdl-color--grey-100">
    <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">

      <!-- Container for the Table of content -->
      <div class="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--12-col-desktop">
        <div class="mdl-card__supporting-text mdl-color-text--grey-600">
          <!-- div to display the generated Instance ID token -->
          <div id="token_div" style="display: none;">
            <h4>Instance ID Token</h4>
            <p id="token" style="word-break: break-all;"></p>
            <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored"
                    onclick="deleteToken()">Delete Token</button>
          </div>
          <!-- div to display the UI to allow the request for permission to
               notify the user. This is shown if the app has not yet been
               granted permission to notify. -->
          <div id="permission_div" style="display: none;">
            <h4>Needs Permission</h4>
            <p id="token"></p>
            <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored"
                    onclick="requestPermission()">Request Permission</button>
          </div>
          <!-- div to display messages received by this app. -->
          <div id="messages"></div>
        </div>
      </div>

    </div>
  </main>
</div>

<!-- Import and configure the Firebase SDK -->
<!-- These scripts are made available when the app is served or deployed on Firebase Hosting -->
<!-- If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup -->
<!-- <script src="/__/firebase/5.0.0/firebase-app.js"></script>
<script src="/__/firebase/5.0.0/firebase-messaging.js"></script>
<script src="/__/firebase/init.js"></script> -->
<script src="https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js"></script>
<script>
  firebase.initializeApp({
    'messagingSenderId': 'yoursenderid'
  });
</script>
<script>
  // [START get_messaging_object]
  // Retrieve Firebase Messaging object.
  const messaging = firebase.messaging();
  // [END get_messaging_object]
  // [START set_public_vapid_key]
  // Add the public key generated from the console here.
  // messaging.usePublicVapidKey('<YOUR_PUBLIC_VAPID_KEY_HERE>');
  // [END set_public_vapid_key]

  // IDs of divs that display Instance ID token UI or request permission UI.
  const tokenDivId = 'token_div';
  const permissionDivId = 'permission_div';

  // [START refresh_token]
  // Callback fired if Instance ID token is updated.
  messaging.onTokenRefresh(function() {
    messaging.getToken().then(function(refreshedToken) {
      console.log('Token refreshed.');
      // Indicate that the new Instance ID token has not yet been sent to the
      // app server.
      setTokenSentToServer(false);
      // Send Instance ID token to app server.
      sendTokenToServer(refreshedToken);
      // [START_EXCLUDE]
      // Display new Instance ID token and clear UI of all previous messages.
      resetUI();
      // [END_EXCLUDE]
    }).catch(function(err) {
      console.log('Unable to retrieve refreshed token ', err);
      showToken('Unable to retrieve refreshed token ', err);
    });
  });
  // [END refresh_token]

  // [START receive_message]
  // Handle incoming messages. Called when:
  // - a message is received while the app has focus
  // - the user clicks on an app notification created by a service worker
  //   `messaging.setBackgroundMessageHandler` handler.
  messaging.onMessage(function(payload) {
    console.log('Message received. ', payload);
    // [START_EXCLUDE]
    // Update the UI to include the received message.
    appendMessage(payload);
    // [END_EXCLUDE]
  });
  // [END receive_message]

  function resetUI() {
    clearMessages();
    showToken('loading...');
    // [START get_token]
    // Get Instance ID token. Initially this makes a network call, once retrieved
    // subsequent calls to getToken will return from cache.
    messaging.getToken().then(function(currentToken) {
      if (currentToken) {
        sendTokenToServer(currentToken);
        updateUIForPushEnabled(currentToken);
      } else {
        // Show permission request.
        console.log('No Instance ID token available. Request permission to generate one.');
        // Show permission UI.
        updateUIForPushPermissionRequired();
        setTokenSentToServer(false);
      }
    }).catch(function(err) {
      console.log('An error occurred while retrieving token. ', err);
      showToken('Error retrieving Instance ID token. ', err);
      setTokenSentToServer(false);
    });
    // [END get_token]
  }


  function showToken(currentToken) {
    // Show token in console and UI.
    var tokenElement = document.querySelector('#token');
    tokenElement.textContent = currentToken;
  }

  // Send the Instance ID token your application server, so that it can:
  // - send messages back to this app
  // - subscribe/unsubscribe the token from topics
  function sendTokenToServer(currentToken) {
    if (!isTokenSentToServer()) {
      console.log('Sending token to server...');
      // TODO(developer): Send the current token to your server.
      setTokenSentToServer(true);
    } else {
      console.log('Token already sent to server so won\'t send it again ' +
          'unless it changes');
    }

  }

  function isTokenSentToServer() {
    return window.localStorage.getItem('sentToServer') === '1';
  }

  function setTokenSentToServer(sent) {
    window.localStorage.setItem('sentToServer', sent ? '1' : '0');
  }

  function showHideDiv(divId, show) {
    const div = document.querySelector('#' + divId);
    if (show) {
      div.style = 'display: visible';
    } else {
      div.style = 'display: none';
    }
  }

  function requestPermission() {
    console.log('Requesting permission...');
    // [START request_permission]
    messaging.requestPermission().then(function() {
      console.log('Notification permission granted.');
      // TODO(developer): Retrieve an Instance ID token for use with FCM.
      // [START_EXCLUDE]
      // In many cases once an app has been granted notification permission, it
      // should update its UI reflecting this.
      resetUI();
      // [END_EXCLUDE]
    }).catch(function(err) {
      console.log('Unable to get permission to notify.', err);
    });
    // [END request_permission]
  }

  function deleteToken() {
    // Delete Instance ID token.
    // [START delete_token]
    messaging.getToken().then(function(currentToken) {
      messaging.deleteToken(currentToken).then(function() {
        console.log('Token deleted.');
        setTokenSentToServer(false);
        // [START_EXCLUDE]
        // Once token is deleted update UI.
        resetUI();
        // [END_EXCLUDE]
      }).catch(function(err) {
        console.log('Unable to delete token. ', err);
      });
      // [END delete_token]
    }).catch(function(err) {
      console.log('Error retrieving Instance ID token. ', err);
      showToken('Error retrieving Instance ID token. ', err);
    });

  }

  // Add a message to the messages element.
  function appendMessage(payload) {
    const messagesElement = document.querySelector('#messages');
    const dataHeaderELement = document.createElement('h5');
    const dataElement = document.createElement('pre');
    dataElement.style = 'overflow-x:hidden;';
    dataHeaderELement.textContent = 'Received message:';
    dataElement.textContent = JSON.stringify(payload, null, 2);
    messagesElement.appendChild(dataHeaderELement);
    messagesElement.appendChild(dataElement);
  }

  // Clear the messages element of all children.
  function clearMessages() {
    const messagesElement = document.querySelector('#messages');
    while (messagesElement.hasChildNodes()) {
      messagesElement.removeChild(messagesElement.lastChild);
    }
  }

  function updateUIForPushEnabled(currentToken) {
    showHideDiv(tokenDivId, true);
    showHideDiv(permissionDivId, false);
    showToken(currentToken);
  }

  function updateUIForPushPermissionRequired() {
    showHideDiv(tokenDivId, false);
    showHideDiv(permissionDivId, true);
  }

  resetUI();
</script>
</body>
</html>

index.html의 전체코드인데 단지 수정해야할 부분은

<script>
  firebase.initializeApp({
    'messagingSenderId': 'yoursenderid'
  });
</script>

이부분이다. yoursenderid에다가 파베에서 부여해준 SenderId값을 넣어주면 된다.

그리고 firebase-messaging-sw.js에는

// Import and configure the Firebase SDK
// These scripts are made available when the app is served or deployed on Firebase Hosting
// If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
// importScripts('/__/firebase/5.0.0/firebase-app.js');
// importScripts('/__/firebase/5.0.0/firebase-messaging.js');
// importScripts('/__/firebase/init.js');

// var messaging = firebase.messaging();

/**
 * Here is is the code snippet to initialize Firebase Messaging in the Service
 * Worker when your app is not hosted on Firebase Hosting.
**/
 // [START initialize_firebase_in_sw]
 // Give the service worker access to Firebase Messaging.
 // Note that you can only use Firebase Messaging here, other Firebase libraries
 // are not available in the service worker.
 importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
 importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');

 // Initialize the Firebase app in the service worker by passing in the
 // messagingSenderId.
 firebase.initializeApp({
   'messagingSenderId': '696758097282'
 });
/**
 // Retrieve an instance of Firebase Messaging so that it can handle background
 // messages.
 **/
 const messaging = firebase.messaging();
 // [END initialize_firebase_in_sw]
 


// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.setBackgroundMessageHandler(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
  // Customize notification here
  var notificationTitle = 'Background Message Title';
  var notificationOptions = {
    body: 'Background Message body.',
    icon: '/firebase-logo.png'
  };

  return self.registration.showNotification(notificationTitle,
    notificationOptions);
});
// [END background_handler]

이런 코드값이 있다.

터미널에 우선 local이 연결되어있으니 다른 터미널을 하나 더 띄워서 fcm-cli를 설치해야한다.

npm i -g fcm-cli

설치한다. 그리고 터미널에서

fcm send --server-key 서버키 --to 인스턴스토큰 --notification.title hi --notification.body hello

라고 명령하면 터미널에서 PUSH> Succeeded: Multicast ID: ======

Success:1

Failure :0

Canonical Ids: 0

Message Id: blahblahblah

라고 뜬다.

하지만!

이건 내가 원하는게 아니다. 내가 원하는건 옆에 푸시알림이 뜨는걸 말한거다..! 브라우저에 내용이 나오는건 중요하지 않단말이다.

그래서 firebase-messaging-sw.js에다가 코드를 더 추가했다.

self.addEventListener('push', function(event) {
  console.log('[Service Worker] Push Received.');
  console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);

  const title = 'congrats!';
  const options = {
    body: 'this is push msg!',
    icon: '/image/icons/icon-512x512.png'
  };
const notificationPromise = self.registration.showNotification(title, options);
event.waitUntil(notificationPromise);
});

self.addEventListener('notificationclick', function(event) {
  console.log('[Service Worker] Notification click Received.');

  event.notification.close();

  event.waitUntil(
    clients.openWindow('https://mhome2.pro1.co.kr')
  );
});

위 코드를 넣으면 터미널에서 다시 명령을 하고 엔터를 치면

pushnoti3

이라고 노티가 뜬다..! 오예오예

그러면 브라우저에서도

성공

라면서 화면에 내용이 뜨고, 콘솔에는 Message received라고 하며 상세내용이 나온다.

저번에는 이걸 일하고있는데도 적용해서 메인페이지 리로드할때마다 뜨게 만들었는데, 그게 기억이 1도안난다. 이제 예제는 해결했으니까 실전 삽질하러 가야지!!

유림님께서 그거 했다고하니까 받는로직은 된것같은데 보내는 로직이 안된것 같다고 그것만 하면 될것같은데..! 그게 진심 엄청 큰 산…ㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎ

asp fcm 알아봐야지…아니면 asp gcm.. 구글신님은 모든걸 알고계실거야 그럴거야..




© 2018. by sora

Powered by sora