[오류] Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker

호이초이·2024년 10월 24일
1

Writon 서비스 웹 푸시알림을 적용하다 오류가 발생했다.

1. 문제 발생

웹 푸시 알림을 구현하다가 에러가 발생했다. 그건 바로 파이어베이스 측으로부터 디바이스 토큰을 받아오는 과정에서 에러가 발생했다 안했다 하는 것이다.

동작과정 : 푸시 알림 허용 -> 서비스 워커 등록 -> 파이어베이스 측 디바이스토큰 받기 -> 디바이스 토큰 서버에 보내주기

현재 내 푸시알림은 이렇게 동작한다.

근데,, 푸시알림을 허용함과 동시에 자꾸 아래와 같은 에러가 떴다 안떴다 하는 것이다.

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

에러가 또 발생하는 것이다.!


2. 문제 분석 및 서칭

그래서 난 구글링 통해 해당 에러를 검색해보았다.
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부터 디바이스토큰을 다 받아오는 방식으로 해결을 했다!

아래 사진처럼 말이다!

해결완료!!

뭔가 나만의 방식으로 해결한 것을 다른 사람들에게도 알리고자 해당 깃허브 이슈에 댓글을 남겼다.! (영어로 변역해서!)

profile
칼을 뽑았으면 무라도 썰자! (근데 아직 칼 안뽑음)

0개의 댓글