๐Ÿฅ Web

[FCM] Firebase Cloud Messaging Vanilla JS + FastAPI์— ์ ์šฉํ•˜๊ธฐ

darly213 2024. 4. 25. 17:04
728x90

FCM ์„ค์ •

 

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ Firebase ํด๋ผ์šฐ๋“œ ๋ฉ”์‹œ์ง• ํด๋ผ์ด์–ธํŠธ ์•ฑ ์„ค์ •

Google I/O 2023์—์„œ Firebase์˜ ์ฃผ์š” ์†Œ์‹์„ ํ™•์ธํ•˜์„ธ์š”. ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ ์˜๊ฒฌ ๋ณด๋‚ด๊ธฐ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ Firebase ํด๋ผ์šฐ๋“œ ๋ฉ”์‹œ์ง• ํด๋ผ์ด์–ธํŠธ ์•ฑ ์„ค์ • ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ

firebase.google.com

 

1. Firebase CDN ์ถ”๊ฐ€

์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ด๋ดค๋Š”๋ฐ ๊ฐ€์žฅ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

<!-- index.html -->
<script type="module">
        import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.11.0/firebase-app.js'
        import { getMessaging, getToken, onMessage } from 'https://www.gstatic.com/firebasejs/10.11.0/firebase-messaging.js'
        ...

์ฒ˜์Œ์—๋Š” CDN ํ™œ์šฉํ•ด์„œ

<script src="https://cdnjs.cloudflare.com/ajax/libs/firebase/10.11.0/firebase-app.js" integrity="sha512-JcwDQxh56yOxNHIafGu2ykBz2TUF9vsGSySz72SwfN4XSm/4jls/vPWV5wPWKTekXPtPHejHN7jUBCdJB0Yehw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

์ด๋ ‡๊ฒŒ๋„ ํ•ด๋ดค๋Š”๋ฐ, ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์„œ module ๋ฐฉ์‹์œผ๋กœ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

2. Firebase Config ์ถ”๊ฐ€ ๋ฐ ์ดˆ๊ธฐํ™”

  • ํ”„๋กœ์ ํŠธ ์„ค์ •์—์„œ config๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Œ.

// index.html
// appKey = VAPID Key
const appKey = "YOUR_VAPID_KEY";
const config = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_PROJECT_ID.appspot.com",
  messagingSenderId: "YOUR_SENDER_ID",
  appId: "YOUR_APP_ID",
  measurementId: "YOUR_MEASUREMENT_ID",
};

initializeApp(config);
const messaging = getMessaging();

๊ทธ๋ฆฌ๊ณ  ์•Œ๋ฆผ ๊ถŒํ•œ์„ ๋ฐ›๋Š”๋‹ค. ๊ณผ๊ฑฐ firebase ๋ฒ„์ „์—์„œ๋Š” messaging์„ ํ†ตํ•ด์„œ ๊ถŒํ•œ์„ ๋ฐ›์•˜๋˜ ๊ฒƒ ๊ฐ™์€๋ฐ, ๊ทผ๋ž˜์—๋Š” ๋ฐ”๋€Œ์—ˆ๋‹ค.

// index.html
Notification.requestPermission().then((permission) => {
    if (permission === 'granted') {
        console.log('Notification permission granted.');
        if (isTokenSentToServer()) {
            console.log('Token already sent to server');
        }
    } else {
        console.log('Unable to get permission to notify.');
    }
}).catch(function (err) {
    console.log('Unable to get permission to notify.', err);
});

 

3. ์›น ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ๊ตฌ์„ฑ

  • ์ž๋ฐœ์  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„ ID(VAPID ํ‚ค) ๋ผ๋Š” ์›น ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๋ฅผ ํ†ตํ•ด ์›น ํ‘ธ์‹œ ์„œ๋น„์Šค ๋ณด๋‚ด๊ธฐ ์š”์ฒญ ์Šน์ธ
  • ํ‚ค ์Œ์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ธฐ์กด ๊ฒƒ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Œ

 
  • FCM์„ ์“ฐ๊ธฐ ์œ„ํ•ด์„œ Server Key(VAPID key) ๋ผ๋Š” ๊ฒŒ ํ•„์š”ํ•œ๋ฐ, ์ด๊ฑธ ์œ„ํ•ด์„œ Cloud Messaging API(๊ธฐ์กด) ์ด๋ผ๊ณ  ํ‘œ๋˜์–ด์žˆ๋Š” API๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. ์  ์„ธ ๊ฐœ ๋ˆ„๋ฅด๊ณ  API ๊ด€๋ฆฌ์ž๋กœ ํ”„๋กœ์ ํŠธ์— ์ถ”๊ฐ€ํ•˜์ž.
  • ๊ทธ๋Ÿฌ๊ณ  ๋‚˜๋ฉด Server Key๊ฐ€ ๋‚˜์˜จ๋‹ค.

 

4. getToken์„ ํ†ตํ•ด์„œ ์‚ฌ์šฉ์ž ์ธ์ฆ

  • getToken ์„ ์ด์šฉํ•ด์„œ ๊ธฐ๊ธฐ ์‹๋ณ„์šฉ ํ† ํฐ์„ ๋งŒ๋“ค๊ณ  ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
  • vapidKey ์— server key๋ฅผ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค.
getToken(messaging, { vapidKey: 'YOUR_VAPID_KEY' })
  .then(function (currentToken) {
      if (currentToken) {
          console.log(currentToken);
          setTokenSentToServer(true, currentToken);
      } else {
          setTokenSentToServer(false);
      }
  }).catch(function (err) {
      console.log('An error occurred while retrieving token. ', err);
      showToken('Error retrieving Instance ID token. ', err);
      setTokenSentToServer(false);
  });

5. firebase-messaging-sw.js ๋“ฑ๋ก

  • FCM ์‚ฌ์šฉ์„ ์œ„ํ•ด์„œ๋Š” firebase-messaging-sw.js ๋ผ๋Š” ํŒŒ์ผ์ด ๋ฐ˜๋“œ์‹œ ์กด์žฌํ•ด์•ผํ•œ๋‹ค. ์ผ๋‹จ ๋ญ˜ ์‹œ์ž‘ํ•˜๋ ค๊ณ  ํ•˜๋ฉด hostname/firebase-messaging-sw.js ํŒŒ์ผ์„ ์ฐพ์œผ๋ฏ€๋กœ ๋ฃจํŠธ ์•„๋ž˜์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•ด์ค˜์•ผํ•œ๋‹ค. ๋‚˜๋Š” FastAPI๋ฅผ ์‚ฌ์šฉ์ค‘์ด๋ผ์„œ ์„œ๋ฒ„์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ฒฝ๋กœ๋ฅผ ์ถ”๊ฐ€ํ•ด์คฌ๋‹ค.
@app.get("/firebase-messaging-sw.js")
def fcm():
    return FileResponse("firebase-messaging-sw.js")
  • service worker ํŒŒ์ผ์—์„œ๋Š” importScripts ๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ถ™์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.
// firebase-messaging-sw.js
importScripts(
  "https://www.gstatic.com/firebasejs/10.11.0/firebase-app-compat.js"
);
importScripts(
  "https://www.gstatic.com/firebasejs/10.11.0/firebase-messaging-compat.js"
);
  • ์ดํ›„๋กœ๋Š” ๋‹ค๋ฅธ ๊ฑด ๋ณ„๋กœ ์—†๋‹ค. ๋ฉ”์ธ js ํŒŒ์ผ์—์„œ ์„ค์ •ํ•ด์คฌ๋˜๋Œ€๋กœ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
// firebase-messaging-sw.js
firebase.initializeApp({
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_PROJECT_ID.appspot.com",
  messagingSenderId: "YOUR_SENDER_ID",
  appId: "YOUR_APP_ID",
  measurementId: "YOUR_MEASUREMENT_ID",
});

const messaging = firebase.messaging();

https://medium.com/@cizu64/sending-notification-message-with-firebase-with-javascript-5d5c4bb02206

 

Firebase Push Notification Message with JavaScript

Introduction to Firebase

medium.com

 

6. ๋ฉ”์„ธ์ง€ ์ˆ˜์‹  ์„ค์ •

  • ๋ฉ”์„ธ์ง€ ์ˆ˜์‹  ์„ค์ •์—๋Š” onMessage ์™€ onBackgroundMessage ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.
  • onMessage: Foreground, focus๊ฐ€ ํ•ด๋‹น ์„œ๋น„์Šค์— ๊ฐ€์žˆ๋Š” ๊ฒฝ์šฐ(์ฐฝ์ด ๋– ์žˆ์„ ๋•Œ) ๋ฐ›์€ ๋ฉ”์„ธ์ง€ ์ฒ˜๋ฆฌ
    • ๋ฉ”์ธ js์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผํ•œ๋‹ค.
  • onBackgroundMessage: Background, ํ•ด๋‹น ์„œ๋น„์Šค๋ฅผ ๋ฒ—์–ด๋‚˜์žˆ๋Š” ๊ฒฝ์šฐ์— ๋ฐ›์€ ๋ฉ”์„ธ์ง€ ์ฒ˜๋ฆฌ
    • service worker์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผํ•œ๋‹ค.

! ์ •์˜ํ•ด์•ผํ•˜๋Š” ํŒŒ์ผ ์™ธ์—์„œ ์ •์˜ํ•˜๋ฉด ์˜ค๋ฅ˜ ๋ฐœ์ƒํ•˜๋‹ˆ ์ฃผ์˜

// index.js
onMessage(messaging, (payLoad) => {
    console.log("Message Received");
    console.log(payLoad);
    var notificationTitle = payLoad.notification.title;
    var notificationOptions = {
        body: payLoad.notification.body,
        icon: payLoad.notification.icon,
    };
    var notification = new Notification(notificationTitle, notificationOptions);
});
// firebase-messaging-sw.js
messaging.onBackgroundMessage(function (payload) {
  console.log(
    "[firebase-messaging-sw.js] Received background message ",
    payload
  );

  const notificationTitle = payLoad.notification.title;
  const notificationOptions = {
    body: payLoad.notification.body,
    icon: payLoad.notification.icon,
  };

  return self.registration.showNotification(
    notificationTitle,
    notificationOptions
  );
});

 

7. Firebase console์—์„œ ํ…Œ์ŠคํŠธ ๋ฉ”์„ธ์ง€ ๋ณด๋‚ด๊ธฐ

  • ์ฒซ๋ฒˆ์งธ ์บ ํŽ˜์ธ ๋งŒ๋“ค๊ธฐ - firebase ์•Œ๋ฆผ ๋ฉ”์„ธ์ง€ ์„ ํƒ

  • ๋Œ€๋žต์ ์ธ ๋‚ด์šฉ ์ž‘์„ฑ ํ›„ ํ…Œ์ŠคํŠธ ๋ฉ”์„ธ์ง€ ์ „์†ก ํด๋ฆญ
  • ์œ„์—์„œ ์ž‘์„ฑํ•œ ๋‚ด์šฉ์—์„œ getToken ์„ ํ†ตํ•ด์„œ ๋ฐ›์€ token์„ FCM ๋“ฑ๋ก ํ† ํฐ ์ถ”๊ฐ€ ๋ž€์— ๋ณต๋ถ™

  • ํ…Œ์ŠคํŠธ ํด๋ฆญ

  • ๋ฉ”์„ธ์ง€ ๋„์ฐฉ ํ™•์ธ!!
  • ์ฐฝ์„ ๋‹ค๋ฅธ ํƒญ์œผ๋กœ ๋Œ๋ ค๋‘๊ณ  background์—์„œ๋„ ์ž˜ ๋„์ฐฉํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ

 

0. ๋งˆ์ฃผ์นœ ์˜ค๋ฅ˜ ๋ชฉ๋ก

FirebaseError: Messaging: A problem occurred while unsubscribing the user from FCM: Requested entity was not found.
  • chrome์—์„œ๋งŒ ๋ฐœ์ƒํ•˜๋‚˜?
  • ์ƒ๋‹นํžˆ ๊ณ ์งˆ์ ์ธ ๋ฌธ์ œ๋กœ ํŒŒ์•…๋จ.
title: "firebase.messaging.getToken() edgecase when push notification is set back to default (ask) · Issue #2364 · firebase/firebase-js-sdk"
image: "https://opengraph.githubassets.com/03116c0e7b563d83b2956dd4dd240aafe3681c25a9141c528f6dbf89d1dbfd6c/firebase/firebase-js-sdk/issues/2364"
description: "[REQUIRED] Describe your environment Operating System version: macOS Mojave Version 10.14.6 Browser version: Google Chrome Version 78.0.3904.97 Firebase SDK version: 7.0.0 Firebase Product: Firebas…"
url: "https://github.com/firebase/firebase-js-sdk/issues/2364"
  • ์•Œ๋ฆผ ๊ถŒํ•œ ์„ค์ •์„ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด ํ•ด๊ฒฐ๋˜๊ธด ํ•จ
messaging.setbackgroundmessagehandler is not a function
  • setBackgroundMessageHandler ๋Š” onBackgroundMessage ๋กœ ๋ณ€๊ฒฝ๋จ
firebase messaging domexception failed to execute 'atob' on 'window' the string to be decoded is not correctly encoded
  • VAPID ์ œ๋Œ€๋กœ ๋„ฃ์€ ๊ฒŒ ๋งž๋Š”์ง€ ํ™•์ธ
messaging request permission is not a function
  • firebase messaging ์˜ requestPermission ์€ Notification.requestPermission ์œผ๋กœ ๋Œ€์ฒด๋จ
uncaught syntaxerror: unexpected token 'export' (at firebase-app.js:2520:1)
  • script๋ฅผ module๋กœ ์ ์šฉํ•ด์•ผ๋งŒ ์œ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ.
  • module์ด ์•„๋‹ˆ๋ผ ์ผ๋ฐ˜์ ์œผ๋กœ ์ ์šฉํ•˜๊ณ  ์‹ถ์œผ๋ฉด firebase-app-combat.js ์‚ฌ์šฉ ๊ฐ€๋Šฅ
    • ๊ทผ๋ฐ ์ €๋Š” ์ž˜ ์•ˆ๋์Šต๋‹ˆ๋‹ค...
firebase-messaging-sw.js:1 uncaught domexception: failed to execute 'importscripts' on 'workerglobalscope'
  • firebase serviceworker๋Š” ๋”ฐ๋กœ ๋“ฑ๋กํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค.
the script resource is behind a redirect, which is disallowed.
  • hostname/firebase-messaging-sw.js ๊ฒฝ๋กœ๋กœ ํŒŒ์ผ์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ์ง€ ํ™•์ธ
728x90