웹 워커와 서비스 워커 (2)

Caesars·2023년 1월 16일
1

JS

목록 보기
6/8

지난 글에 이어 서비스 워커를 이용한 웹 푸시 서비스를 구현했습니다. 서비스 워커의 권한이 더 많은 탓인지 구현 과정에서 웹 워커에 비해 제한 사항이 많았습니다.


푸시에 대한 이해

클라이언트(브라우저)에서 푸시 서비스를 구독하고 어플리케이션 서버에서는 메시지 데이터를 푸시 서비스로 전달하면 푸시 서비스에서 사용자 정보를 식별한 후 클라이언트로 전달합니다.

크롬, 파폭 등 브라우저마다 자체적인 푸시 서비스를 위한 서버를 제공합니다. 클라이언트는 각 브라우저의 푸시 서비스를 구독하는 방식이기에 직접 서버를 구축하거나 유지보수하는 부담이 없습니다.

  • 1) 브라우저에서 VAPID를 가지고 푸시 서비스를 구독
  • 2) 구독 정보 수신(등록 토큰)
  • 3) 구독 정보를 어플리케이션 서버로 전달
  • 4) 구독 정보 + VAPID 로 구성된 메시지 발송
  • 5) VAPID 키로 검증된 경우 브라우저로 푸시 메시지 발송

푸시 서비스 구현

  • Firebase 설정 : Google Firebase에 프로젝트를 추가하고 필요 설정을 진행합니다.
  • Client-side 구현 : 브라우저 내 js에서 서비스 워커를 등록하고 Push 알림 수신 이벤트를 받습니다.
  • Server-side 구현 : SpringBoot 프로젝트에 설정파일을 등록하고 Push 알림 발신 서비스를 구현합니다.

Firebase 설정

파이어베이스 에 접속해 프로젝트를 생성합니다.

프로젝트 생성 후 웹 앱을 추가합니다.

프로젝트 설정 -> 클라우드 메시징 탭에서 인증서를 생성 합니다.

Client-side 구현

지난 웹 워커 글에서는 JavaScript Blob 파일을 동적으로 생성해 워커를 등록했지만 서비스 워커의 경우에는 불가능합니다. 같은 호스트 주소의 정적 파일만 등록 가능합니다.

mainScript.js

서비스 워커 등록, 토큰 발급, 포그라운드 메시지 수신을 구현하는 코드입니다. 토큰을 얻기 위해서는 vapid가 필요한데, 이 값은 프로젝트설정 > 클라우드메시징 > 웹 구성의 웹푸시인증서 발급 에서 발급받을 수 있습니다.

import { initializeApp } from "//www.gstatic.com/firebasejs/9.15.0/firebase-app.js";
import { getAnalytics } from "//www.gstatic.com/firebasejs/9.15.0/firebase-analytics.js";
import { getMessaging, getToken, onMessage } from "//www.gstatic.com/firebasejs/9.15.0/firebase-messaging.js";

// 파이어베이스 프로젝트 생성후 받은 설정 정보를 붙여넣습니다.
const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    ...
  };

// 서비스워커 등록
if ('serviceWorker' in window.navigator) {
  window.navigator.serviceWorker.register('../firebase-messaging-sw.js', {type : 'module'})
    .then(function(registration) {
    	console.log('Registration successful, scope is:', registration.scope);
    	initFirebase();
  	}).catch(function(err) {
    	console.log('Service worker registration failed, error:', err);
  	});
}
  
function initFirebase() {
    
    const app = initializeApp(firebaseConfig);
    const messaging = getMessaging(app);
    const analytics = getAnalytics(app);
    
    getToken(messaging, {
      vapidKey: "",
    })
      .then((currentToken) => {
      	if (currentToken) {
          console.log(currentToken);
        } else {
          console.log("No registration token available. Request permission to generate one.");
        }
      })
      .catch((err) => {
        console.log("An error occurred while retrieving token. ", err);
      });
    
    onMessage(messaging, (payload) => {
      console.log("Message received. ", payload);
      
    });
}

firebase-messaging-sw.js

importScripts('https://www.gstatic.com/firebasejs/9.15.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/9.15.0/firebase-messaging-compat.js');

const firebaseConfig = {
  };

// Initialize Firebase
firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
  console.log(
    "[firebase-messaging-sw.js] Received background message ",
    payload
  );
  
  // Customize notification here
  const notificationTitle = "Background Message Title";
  const notificationOptions = {
    body: payload,
    icon: "/firebase-logo.png",
  };
  
  self.registration.showNotification(notificationTitle, notificationOptions);
});

Server-side 구현

build.gradle

라이브러리 추가

implementation 'com.google.firebase:firebase-admin:7.1.1'

application.yaml

파이어베이스 프로젝트 생성 후 발급받은 인증서 파일을 프로젝트 내에 저장합니다.

spring:
  cloud:
    gcp:
      fcm : gcp-webpush-key.json

FCMInitializer.java

파이어베이스 앱 설정

@Component
public class FCMInitializer {
    
    @Value("${spring.cloud.gcp.fcm}")
    private String googleApplicationCredentials;

    @PostConstruct
    public void initialize() throws IOException {

        ClassPathResource resource = new ClassPathResource(googleApplicationCredentials);

        try (InputStream is = resource.getInputStream()) {
            FirebaseOptions options = FirebaseOptions.builder()
                .setCredentials(GoogleCredentials.fromStream(is))
                .build();

            if (FirebaseApp.getApps().isEmpty()) {
                FirebaseApp.initializeApp(options);
            }
        }
        
    }
}

FCMService.java

@Service
public class FCMService {
    
    public void sendCompletedMessage(String email) {
        Message message = Message.builder()
                .putData("title", "title")
                .putData("content", "content")
                .setToken("")
                .build();

        FirebaseMessaging.getInstance().sendAsync(message);
    }
    
}

알림 수신 테스트

서버에서 푸시 알림을 보내면 아래와 같이 메시지를 받을 수 있습니다.

포어그라운드에서 수신시에는 로그를 찍는 것 외에는 별도의 작업을 구현하지 않았습니다.

백그라운드에서 수신시에는 아래와 같이 알림창이 나타납니다.

현재 상태가 백그라운드인지 아닌지를 판단하는 기준은 페이지가 보이는지 아닌지로 생각하시면 됩니다. 페이지가 열려있어도 다른 탭을 띄어놨다면 백그라운드 상태입니다. 하단의 코드와 함께 테스트해보면 쉽게 이해되실 겁니다.

document.addEventListener('visibilitychange', () => {
	if (document.visibilityState === 'visible') {
    	console.log('포어그라운드 상태');
        return;
    }
    
    if (document.visibilityState === 'hidden') {
    	console.log('백그라운드 상태');
    }
});

추가로 데스크톱 기기에서 브라우저가 실행되고 있지 않으면 푸시 알림을 받을 수 없습니다. 웹 푸시 알림은 구독자가 온라인 상태이고 브라우저가 실행 중인 경우에만 전달할 수 있습니다.

웹사이트 방문자가 푸시 알림을 구독하면 고유한 ID가 할당됩니다. 그리고 그의 메타데이터(운영 체제, 브라우저, 국가, 도시, 언어)는 웹사이트 서버에 저장됩니다. 이 데이터는 대상 캠페인에 대한 구독자를 분류하는 데 사용됩니다.

푸시 이력 확인

푸시를 보내고 나면 이력을 확인하고 싶으실 겁니다. 하지만 콘솔에서 보낸 메시지는 메시징 탭에서 이력이 나오는데 API를 통해 보낸 메시지는 이력이 나오지 않습니다. 스택오버플로에 올라온 글들을 보면 다른 사람들도 비슷한 문제를 겪었고 google cloud 내의 BigQuery와 결합해 이력을 확인하는 방법을 추천해 줬습니다.

BigQuery 연동

프로젝트 설정 -> 통합 -> BigQuery 탭에서 Cloud Messagign 체크를 누르면 됩니다.

주의할 점은 기본 요금제로 사용했을 시 메시지가 쌓이지 않습니다. 반드시 Blaze 요금제를 사용해야 BigQuery와 연동되고 데이터가 쌓입니다. 유료 요금제를 선택해도 무료 요금제에서 제공하는 사용량만큼은 공제 된다고 하니 테스트용으로는 부담 없습니다.

정상적으로 연동이 되었다면 데이터셋과 테이블이 자동적으로 추가됩니다.


테이블에서 message_id별로 이력 추적이 가능합니다.

마치며

가끔 웹 사이트 내에서 실시간 알림(인기글, 상품정보)을 봤을때 어떻게 구현했는지 궁금했는데 찾아보니 대부분 웹푸시가 아니라 웹소켓으로 이었습니다. 유저가 따로 알림 설정을 켜야 하는 문제 때문이지 않을까 싶네요. 웹푸시는 유저가 사이트를 닫아도 알림을 받을 수 있다는 장점이 있지만 아마 대부분의 유저들은 알림을 귀찮아 하지 않을까 싶습니다. 광고 서비스에 적용하려면 먼저 적절한 타겟팅 설정이 필수로 보입니다.

참고

https://nsinc.tistory.com/218
https://anywaydevlog.tistory.com/93#public%-Ffirebase-messaging-sw-js
https://developer.mozilla.org/ko/docs/Web/API/Service_Worker_API/Using_Service_Workers
https://stackoverflow.com/questions/40340076/firebase-notification-records-log-api
https://geundung.dev/114
https://velog.io/@idojustdo_it/Web-push%EB%9E%80
https://okky.kr/articles/648517

profile
잊기전에 저장

0개의 댓글