PWA 앱에 Firebase Cloud Message(FCM)을 활용하여 push notification을 구현하고자 했다.
FCM은 다음과 같이 동작한다.
> 클라이언트, firebase로부터 vapid key를 통해 토큰 발급 (for기기 식별)
> 서버, 토큰 저장
> 서버, 알림을 보내야할 때 firebase로 토큰 정보와 내용을 함께 전송
> FCM, 받은 토큰의 유저에게 해당 내용을 전송
이를 구현하면서 겪은 우당탕 이슈들이다.
일단 내 환경은 PWA를 위한 서비스 워커 설정 파일인 public/pwabuilder-sw.js
와 내 firebase configuration 정보와 messaging 객체가 담겨있는 public/firebase-messaging-sw.js
두개의 파일이 존재하고, public/index.html
에 pwabuilder-sw.js
를 아래와 같이 register 해두었다.
// public/index.html
...
<script>
(async function () {
if ("serviceWorker" in navigator) {
let registration = await navigator.serviceWorker.getRegistration();
if (!registration) {
registration = await navigator.serviceWorker
.register("./pwabuilder-sw.js")
}
}
})();
</script>
브라우저에서 제공하는 알림 허가 모달은 사용자 경험을 해치지 않도록 최초 한번만 제공된다고 한다 (처음에 다시 안 뜨는 이유가 내 코드 때문인줄 알고 헤맸었다) Stackoverflow
// public/firebase-messaging-sw.js
const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_PROJECT_ID,
storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
});
fcm을 위해 반드시 /public
에 firebase-messaging-sw.js
라는 파일을 두고, firebase app을 초기화해야했다.
그리고 그 과정 속에서 내 firebase configuration들이 그대로 노출될 수 밖에 없는데, 이런 정보들은 당연히 감추어야한다는 생각에 무작정 .env
에 두고 꺼내 쓰려했다.
/public
에서는 .env
파일을 읽을 수가 없다.
그래서 FCM에 꼭 필요한 정보인 apiKey, projectId, messagingSenderId, appId만 담아서 올리기로 했다.
// public/firebase-messaging-sw.js
const app = firebase.initializeApp({
apiKey: "xxxx",
projectId: "xxxx",
messagingSenderId: xxxx,
appId: "xxxx",
});
문제 현상
getToken()
호출시 아래와 같은 에러 발생.getToken()
호출시 토큰이 받아와짐나와 비슷한 이슈를 겪은 사람이 꽤 있었는데, issue#7693
선배님들께서는 이렇게 해결하셨다고 한다.
getToken()
을 실행한다첫번째 방법은 약간의 편법인 느낌이었고,
두번째 방법은 내 코드에서는 service worker가 늦게 활성화되는 것이 문제가 아니라 그냥 아예 활성화 될 생각이 없어보이고(?) getToken()
을 실행해야 찔려서 설치-활성화되는 느낌이라 getToken()
을 실행하기 전에 service worker 상태에 따라 분리하게 되면 영영 getToken()
이 실행되지 않는게 문제였다. (이게 진짜 문제는 아닐 수 있지만 어쨌든 제대로 동작하지 않았다)
다른 방법이 있겠지하며 분석해보기 시작!
크롬 브라우저의 Application 탭에서 service worker의 상태를 확인해보았다.
[ 처음 등록된 PWA 서비스 워커 상태]
[ getToken()
호출시 서비스 워커 상태]
어라 서비스 워커가 추가로 활성화된다? (분명 서비스 워커는 하나만 등록된다 그랬는데..처음 등록된 PWA 서비스 워커도 활성화 상태로 나타난다. 이건 다른 개념인건가..?)
알고보니 위에 각 워커 상태 상단에 나오는 https://localhost:3000/firebase-cloud-messaging-push-scope
, http://localhost:3000/
가 워커의 적용 범위를 말하는 것이었다. 추가적으로 각 범위당 하나의 서비스워커만 가능하다는 것을 보면 아마 둘의 범위가 달라서 추가로 워커가 활성화될 수 있었던 것 같다. web.dev 서비스워커
일단 여기에서 내가 기존에 등록한 적 없는 firebase-cloud-messaging-push-scope
범위에서 새로운 서비스워커가 돌아가고 있다.
(소스가 firebase-messaging-sw.js
인걸 보아하니 import 해뒀던 firebase 관련 스크립트에서 등록을 해주는것 같다)
아무래도 저 워커가 먼저 설치 되어있어야 토큰을 정상적으로 가져올 수 있는건가?
즉 내가 이해한 실행 순서는 이와 같다.
->src/
내 파일에서getToken()
실행
-> 자동으로firebase-messaging-sw.js
호출 및 실행
->firebase-cloud-messaging-push-scope
범위의 서비스 워커 활성화
여기서 문제는 위 워커가 먼저 활성화되어있어야
getToken()
을 통해 토큰이 정상적으로 가져와지는데,getToken()
을 호출 했을때에만 저 워커가 설치-활성화된다는 그런 역설이...(?)(이게 과연 맞는 소리인지는 나도 모른다)
기존 pwabuilder-sw.js
의 내용들을 firebase-messaging-sw.js
로 합쳐서 먼저 실행해보기로 했다.
워커의 소스만 동일해질 뿐 에러 메시지는 그대로였다.
[워커 상태]
그러면 service worker의 적용 범위를 맞춰보자.
firebase-messaging-sw.js
의 범위를 내가 바꿀 수는 없으니,
pwabuilder-sw.js
의 적용 범위를 바꿔보기로 했다.
if ("serviceWorker" in navigator) {
let registration = await navigator.serviceWorker.getRegistration();
if (!registration) {
registration = await navigator.serviceWorker.register(
"./pwabuilder-sw.js",
{ scope: "/firebase-cloud-messaging-push-scope" }, // 이 부분을 추가
);
}
}
같은 범위에서 워커 파일이 추가로 생성되다 보니 아까처럼 두개의 워커가 돌아가는게 아니라, 기존의 워커가 삭제되고 firebase-messaging-sw.js
에서 등록하는 워커로 업데이트(설치)되면서 토큰이 받아와진다!!! 와!!@!
혹시 기존의 pwa 관련 워커가 삭제되면서 제대로 동작하지 않는 부분이 생길까 했지만, 이미 백그라운드에 캐시된 내용과 firebase-messaging-sw.js
을 통해 등록된 워커의 내용이 달라서 그런지 큰 문제는 없는 것 같았다. (pwabuilder-sw.js
에 오프라인일 경우 미리 준비한 html이 뜨도록 설정해두었는데, 활성화된 워커가 변경된 이후에도 잘 떴기에)
이미 기존에 워커(pwabuilder-sw)가 이미 정상적으로 실행중이기에 새로 설치해야하는 워커(fcm-sw) 활성화되는 시간을 고려하지 않고 토큰을 받아오려 하는건가? 따라서 같은 범위에서 워커가 이미 실행중일 땐, 이를 완전히 대체하기 위해서라도 설치-활성화 단계를 더 기다려주는 것이고?
해결하기는 했지만 사실 아직 정확한 원인은 파악하지 못해서 다소 찜찜한 느낌이다. 하지만 관련 이슈도 아직 오픈되어 있는 이유가 있을 것...
(편법이 싫어 따로 방법을 알아본 거지만, 이것도 편법일지도 모르겠다)
그래도 일단은 토큰을 잘 받아온 것에 만족하며,,
이후에 또 문제 생기면 적으러 와야겠다!
근데 콘솔에는 이런게 떠요 ㅎㅅㅎ
Uncaught (in promise) DOMException: Failed to register a ServiceWorker for scope ('https://k-eum2023.web.app/firebase-cloud-messaging-push-scope') with script ('https://k-eum2023.web.app/auth/pwabuilder-sw.js'): The script has an unsupported MIME type ('text/html').