본문 바로가기

[PWA] FCM으로 알람 기능 구현하기 (2) : 백그라운드

[PWA] FCM으로 알람 기능 구현하기 (2) : 백그라운드

공식 레퍼런스

 

아래 링크와 같이 공식 문서를 정말 하염없이 물고 뜯고 맛 보며 많은 시행착오를 거쳤다. 결과적으로, 공식 문서 참조만으로는 원하는 기능을 구현할 수 없었기 때문에 그와 관련된 내용을 정리해보고자 한다. 이때까지 개발한 기능 중 가장 레퍼런스를 오랫동안 찾아 다니고 많은 시도를 해본 것 같다.

 

https://firebase.google.com/docs/cloud-messaging/js/receive?hl=ko

 

자바스크립트 클라이언트에서 메시지 수신  |  Firebase 클라우드 메시징

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 자바스크립트 클라이언트에서 메시지 수신 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장

firebase.google.com

 

백그라운드 구현

  • root 위치에(public 폴더 내) 'firebase-messaging-sw.js' 파일을 생성한다. (공식 문서 참고)
  • service worker 내부 로직이기 때문에 환경변수나 모듈 import 등은 직접 넣어주어야 한다.

 

firebase-messaging-sw.js
  • 알림을 클릭할시, 서버에서 보내는 url로 이동시키기 위한 핸들러도 구현했다.
  • 특히 백그라운드에 해당 서비스가 존재하는 경우, 해당 탭을 focus 시키고 관련 URL로 이동시키는 로직은 구현하기 위해서 정말 많은 시행착오가 있었다. (아래의 트러블 슈팅 3번 항목 참조)
importScripts(
    "https://www.gstatic.com/firebasejs/9.0.0/firebase-app-compat.js",
);
importScripts(
    "https://www.gstatic.com/firebasejs/9.0.0/firebase-messaging-compat.js",
);

const firebaseConfig = {
  // 필요한 정보 입력
};

const app = firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging(app);

/**
 * messaging.onBackgroundMessage - 앱 사용하지 않는 중 메시지 수신 (백그라운드)
 */

self.addEventListener("notificationclick", (event) => {
    event.notification.close(); // 알림 닫기

    const landing_url = event.notification.data; 
    const newPath = landing_url ? landing_url : `/chat`;

    const urlToOpen = new URL(`https://주소${newPath}`);

    // 비동기 작업을 수행하기 위한 메서드로 아래 Promise가 완료될 때까지 이벤트 수명을 연장
    event.waitUntil(
        clients // 서비스 워커에서 현재 제어하는 클라이언트 목록 
            .matchAll({
                type: "window",
                includeUncontrolled: true, // 제어하고 있지 않은 클라이언트까지 포함 (백그라운드)
            })
            .then((windowClients) => {
                let foundWindowClient = null;
                // 이미 열려 있는 창에서 서비스와 관련된 URL을 찾기 위한 로직 추가
                for (let i = 0; i < windowClients.length; i++) {
                    const client = windowClients[i];

                    if (
                        (new URL(client.url).hostname.includes("docent")) &&
                        "focus" in client
                    ) {
                        foundWindowClient = client;
                        break;
                    }
                }

               // 만약 백그라운드에 해당 서비스가 있다면 
                if (foundWindowClient) {
                    // 해당 탭을 focus하여 이동시킴
                    return foundWindowClient.focus().then((focusedClient) => {
                        if ("navigate" in focusedClient) {
                            // 원하는 주소로 이동
                            focusedClient.postMessage(urlToOpen.href);
                        }
                    });
                    
                // 그게 아니라면 새창을 열어서 원하는 URL로 이동시킴 
                } else if (clients.openWindow) {
                    return clients.openWindow(urlToOpen.href);
                }
            }),
    );
});

messaging.onBackgroundMessage(function (payload) { // FCM 백그라운드 메시지 메서드

    const notificationTitle = payload.data.title;
    const notificationOptions = {
        body: payload.data.body,
        image: payload.data.image_url,
        icon: "주소/icon.png",
        data: payload.data.landing_url, // 클릭 이벤트 핸들링을 위해 data에 url 주소 넣어주기
    };
    self.registration.showNotification(notificationTitle, notificationOptions);
});

 

 

 

💥 트러블 슈팅

 

정말 많은 트러블 슈팅이 있었다... 정리해보니 간결하지만, 그 당시에는 정말 많은 레퍼런스를 찾아다니며 적용해보고 또 적용해보던 인고의 시간을 거쳤다.

 

1.  알림이 중복으로 발생 

  • 이유는 FCM에서 payload에 notification 항목에 데이터를 보낼 경우, 자동으로 알림 객체를 감지하여 알림 메시지를 기본으로 생성해주기 때문이었다.
  • 따라서 아래와 같이 notification 항목이 아닌, data 항목에 필요한 데이터를 보내주는 것으로 해결했다.

  • 레퍼런스
 

Firebase web Push notifications is triggered twice when using onBackgroundMessage()

I really don't know what is going on with these things. I am using FCM web push normally for my website. I can receive the message on the website if I am in foreground or I receive the notification...

stackoverflow.com

 

 

2. notificationclick 에러 메시지 

처음에는 notificationclick 이벤트 관련 내용을 onBackgroundMessage 메서드 안에 넣어주었더니 아래와 같은 에러 메시지가 출력이 되었다.  

Event handler of 'notificationclick' event must be added on the initial evaluation of worker script. (anonymous) @ firebase-messaging-sw.js:27 Et @ sw-listeners.ts:99 await in Et (async) (anonymous) @ register.ts:79
  • Service Worker 스크립트에서 이벤트 핸들러를 메서드 바깥으로 빼고, 초기에 등록해주면서 에러는 해결이 되었다.
  • 레퍼런스
 

Service-Worker Warning in Google Chrome

I am using FCM to send web notifications. i am getting this warning and clicking the notifications does not give the usual result (open the notification url). this is the Service-Worker code

stackoverflow.com

 

3. 원하는 URL로 이동

가장 어려웠던 트러블 슈팅은 해당(💥) 항목이었다. 공식문서에서도, 다른 레퍼런스를 아무리 구글링해봐도 이미 백그라운드에서 동일한 서비스가 열려있는 탭을 포커스시킨 후, 원하는 주소로 리다이렉션하는 코드를 찾기 쉽지 않았다.

 

보통 레퍼런스에서는 아래와 같이 client.navigate 로직을 이용해서 원하는 Path로 이동을 시킨다. 

웹에서는 잘 작동을 했다.

 

하지만, 모바일(웹, PWA)에서는 작동을 하지 않는 것이 문제였다.

모바일 개발자 모드를 켜서 콘솔창을 확인해보니 아래와 같은 문구가 떴다.

This service worker is not the client's active service worker

 

 

이 문제를 해결하기 위해 정말 많은 시도를 해보았지만, 결론적으로는 아래와 같이 postMessage 메서드를 사용하여 해결했다.

  • 중요한 건 현재 active 상태인 서비스 워커로 URL을 보내주면 된다. (거기서 리다이렉트 시키면 해결되므로)
  • 따라서 온클릭 핸들러 안에서 이동시킬 URL을 postMessage 메서드 안에 넣어주었고,

 

// 만약 백그라운드에 해당 서비스가 있다면 
            if (foundWindowClient) {
                // 해당 탭을 focus하여 이동시킴
                return foundWindowClient.focus().then((focusedClient) => {
                    if ("navigate" in focusedClient) {
                        // 💥 원하는 주소로 이동  
                        focusedClient.postMessage(urlToOpen.href);
                    }
                });

 

  • 현재 활성화된 서비스 워커 안에서 해당 메시지를 수신하여 리다이렉션을 시켜주었다.
    navigator.serviceWorker.addEventListener("message", (event) => {
     // event.data = "https://주소/diary/100?type=2" 의 형식으로 온다.
        if (typeof event.data === "string" && event.data.includes("주소")) {
            window.location.href = event.data;
        } 
    });
  • 레퍼런스
 

Service Worker postMessage() Sample

 

googlechrome.github.io

 

 

How to solve service worker navigate "This service worker is not the client's active service worker" error?

I've created a service worker for my quasar pwa to manage fcm web background notifications. The purpose is to manage click on foreground and background notifications and redirect user to specific p...

stackoverflow.com

 

728x90
⬆︎