웹 푸시 알림을 구현하다가 에러가 발생했다. 그건 바로 파이어베이스 측으로부터 디바이스 토큰을 받아오는 과정에서 에러가 발생했다 안했다 하는 것이다.
동작과정 : 푸시 알림 허용 -> 서비스 워커 등록 -> 파이어베이스 측 디바이스토큰 받기 -> 디바이스 토큰 서버에 보내주기
현재 내 푸시알림은 이렇게 동작한다.
근데,, 푸시알림을 허용함과 동시에 자꾸 아래와 같은 에러가 떴다 안떴다 하는 것이다.
Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker
에러 의미 : 서비스워커가 등록되기 전에 디바이스 토큰을 받아오려고 한다는 에러인 것이다!
즉,deviceToken을 받아오는 api에서 에러가 나는 것이다.
export async function handleAllowNotification() {
await Notification.requestPermission();
registerServiceWorker();
try {
console.log("시작");
await getDeviceToken();
console.log("끝");
} catch (error) {
console.error(error);
}
}
async function getDeviceToken() {
// 권한이 허용된 후에 토큰을 가져옴
const token = await getToken(messaging, {
vapidKey:
"BEC7zsAOEMKJz2WH-4N7aq8AuFL5009elrTLnAb4nj37aT1w25d6ZY6uHu1i48vfKiEPO7s-u758Kqhs1cCWjMk",
});
console.log("토큰: ", token);
alert("토큰: " + token);
}
registerServiceWorker();
try {
console.log("시작");
await getDeviceToken();
}
여기서 registerServiceWorker
함수가 서비스워커를 등록하는 함수인데, await 처리를 안해줘서 뜨는 오류인가 했다. 그래서 해당함수를 변경하고 await처리를 해보았다.
//registerServiceWorker.js 파일
export async function registerServiceWorker() {
try {
const registration = await navigator.serviceWorker.register("firebase-messaging-sw.js");
console.log("Service Worker 등록 성공:", registration);
} catch (error) {
console.log("Service Worker 등록 실패:", error);
}
}
try {
await registerServiceWorker();
await getDeviceToken();
}
이렇게 await을 이용해 동기처리를 할 수 있게끔 작업을 했다.
1. 서비스워커가 등록이 된다.
2. 그 후, 디바이스 토큰을 받아온다!
당연히, 될 줄 알고 당당하게 푸시알림 로직을 실행해보았다.!
????? 뭐야
Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker
그래서 난 구글링 통해 해당 에러를 검색해보았다.
https://github.com/firebase/firebase-js-sdk/issues/7575
깃허브 파이어베이스에서도 이 문제에 대해 논의를 하고 있었다! (물론 영어로,,)
여러가지 의견들이 존재했다.
1. 서비스워커가 준비가 된 상태를 확인하기
await navigator.serviceWorker.ready
await getDeviceToken();
navigator.serviceWorker.ready
를 하고 난 후, 디바이스토큰을 불러오면 된다고 나와 있었다.
하지만 이건 해결이 되지 않았다.
2. 에러가 뜬 후, 새로고침하면 다시 된다.
처음 페이지 들어와서 렌더링을 할 경우, 해당 에러가 발생하게 되는데, 새로고침을 하면 서비스워커가 이미 등록되어 있는 상태라 디바이스토큰이 받아와진다는 것이다!
=> 그래서 강제로 새로고침을 하는 방식 의견이 존재했다. 하지만 이건 화면 상 깔끔하지 못하다는 생각이 들었다.
그래서 어떻게 할까 고민하던 중, 문뜩, 이 에러가 발생하면 콜백으로 한 번 더 함수를 실행시킬까? 라는 생각이 들었다.!
해결방안:
getDeviceToken
에서 에러가 발생하면 재시도하는 로직을 추가하는 방식
-> 무한 반복이 될 수 있기 때문에 총 3번의 에러만 담을 수 있도록 로직을 개선해보았다.
await registerServiceWorker(); //서비스 워커 등록하기
const token = await retryGetDeviceToken(3); // 최대 3번까지 재시도
await postDeviceToken(token);www //우리 서버로 토큰 보내기
// getDeviceToken 재시도 로직 추가
async function retryGetDeviceToken(retries: number): Promise<string> {
try {
return await getDeviceToken();
} catch (error) {
if (retries === 0) {
console.error("최대 재시도 횟수 초과:", error);
throw error;
} else {
console.warn(`getDeviceToken 재시도 중... 남은 횟수: ${retries}`);
return retryGetDeviceToken(retries - 1);
}
}
}
retryGetDeviceToken
함수: getDeviceToken 호출 시 에러가 발생하면, 최대 3번까지 재시도하는 로직을 추가
이렇게 하니, 로딩 컴포넌트와 함께 처음 에러가 발생하고 왠만하면 2번째 count부터 디바이스토큰을 다 받아오는 방식으로 해결을 했다!
아래 사진처럼 말이다!
뭔가 나만의 방식으로 해결한 것을 다른 사람들에게도 알리고자 해당 깃허브 이슈에 댓글을 남겼다.! (영어로 변역해서!)