sitelink1 | https://www.omnibuscode.com/board/board_mobile/58290 |
---|---|
sitelink2 | |
sitelink3 |
sitelink1 의 학습을 기반으로 프로젝트에 최종 반영한 샘플 코드이다.
학습하면서 참고한 코드들을 짬뽕했기 때문에 계속해서 수정 보완 중이다. (동작은 하는데 부족한 부분을 보완하고 있다)
게다가 FCM 의 API 가 계속해서 변경되고 있어서 그에 따른 degradation 도 대응하고 있다.
[service-worker.js]
self.oninstall = (e) => {
console.log('[ServiceWorker] installed');
self.skipWaiting();
/**
이벤트내에서 처리해야할 로직이 있고 작업이 끝날때까지 기다려야 한다면 다음과 같이 작성
//'Ripple'이라는 이름으로 캐시를 열고 캐시에 필요한 리소스들을 추가할때까지 기다리게 한다
e.waitUntil(
caches.open('Ripple')
.then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/images/logo.png'
//계속해서 필요한 리소스를 캐싱
]);
})
.then(() => {
console.log('[ServiceWorker] Resources cached');
})
);
*/
}
self.onactivate = (e) => {
console.log('[ServiceWorker] activated');
e.waitUntil(self.clients.claim()); // 제어권획득
/**
새로운 버전의 서비스 워커가 활성화된 경우 발생하는 이벤트이다.
새로운 서비스 워커가 실행되었기 때문에 다음과 같이 캐시를 검사하여 초기화를 할 수 있다.
e.waitUntil(caches.keys()
.then(eacheNames => {
return Promise.all(cacheNames.map
(cacheName => {
if (cacheName !== 'Ripple') {
//다른 버전의 캐시를 정리
return caches.delete(cacheName);
}
}));
}).then(() => {
console.log('[ServiceWorker] delete cache~');
}));
*/
}
let reqDataCount = 0
self.onfetch = (e) => {
console.log('[ServiceWorker] fetch request url', e.request.url);
e.respondWith(fetch(e.request)); // 기존요청 그대로 보내기
/**
필요에 따라 아래와 같이 서버의 리소스 요청을 가로채어 작업을 수행하고
캐시를 활용하여 리소스를 반환하거나 네트워크 요청을 수정할 수 있다.
// data.json 에 대한 요청을 가로채기
if (e.request.url.endsWith('/data.json')) {
reqDataCount++;
e.respondWith(new Response(JSON.stringify({
reqDataCount
}), {
headers: {
'Content-Type': 'application/json'
}
}))
return;
}
// 다음의 코드를 넣으면 무조건 캐시에서 리소스를 반환하도록 강제
e.respondWith(
caches.match(e.request)
.then((response) => {
if(response) { //캐시를 찾으면 캐시를 반환하고 종료
return response;
} else { //캐시가 없으면 기존 요청 그대로 반환
return fetch(e.request);
}
})
.then(error => {
console.error('[ServiceWorker] error!!', error);
})
);
**/
}
//Push Message 수신 이벤트
self.onpush = (e) => {
console.log('[ServiceWorker] 푸시알림 수신: ', e);
console.log('[ServiceWorker] 푸시알림 내용: ', e.data.json().notification);
//Push 정보 조회
const noti = e.data.json().notification;
var title = noti.title || '알림';
var body = noti.body || 'no message';
var icon = noti.icon || '/Images/icon.png'; //512x512
var options = {
body: body,
icon: icon
};
//Notification 출력
e.waitUntil(self.registration.showNotification(title, options));
}
//사용자가 Notification을 클릭했을 때
self.onotificationclick = (e) => {
console.log('[ServiceWorker] 푸시알림 클릭: ', e);
e.notification.close();
e.waitUntil(clients.matchAll({
type: "window"
}).then(function (clientList) {
//실행된 브라우저가 있으면 Focus
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
//실행된 브라우저가 없으면 Open
if (clients.openWindow)
return clients.openWindow('https://localhost:44337/');
}));
};
[index.html]
<!DOCTYPE html>
<html lang="ko" data-bs-theme="auto">
<head>
...
<link rel="manifest" href="manifest.json">
</head>
<body>
<script type="module">
// Import the functions you need from the SDKs you need
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.6.0/firebase-app.js";
import { getMessaging, getToken, onMessage } from "https://www.gstatic.com/firebasejs/10.6.0/firebase-messaging.js";
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "...",
authDomain: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "...",
measurementId: "..."
};
// Initialize Firebase
const firebaseApp = initializeApp(firebaseConfig);
const messaging = getMessaging(firebaseApp);
var isServiceWorkerSupported = 'serviceWorker' in navigator;
if (isServiceWorkerSupported)
{
navigator.serviceWorker.register('service-worker.js', { scope: './'})
.then(reg => {
console.log('[ServiceWorker] 등록 성공: ', reg.scope);
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
console.log('Permission granted');
getToken(messaging, {
vapidKey:
'...',
serviceWorkerRegistration: reg
}).then((token) => {
if (token) {
onMessage(messaging, (payload) => {
console.log('Messaging ', payload);
});
console.log('[FCM Token] token is : ', token);
sendTokenToServer(token);
} else {
console.log('[FCM Token] token is NULL', token);
}
}).catch((err) => {
console.log(err);
});
} else {
console.log('Permission denied');
}
});
})
.catch(function(err)
{
console.log('[ServiceWorker] 등록 실패: ', err);
});
}
</script>
...
</body>
</html>
[JAR]
아래의 jar들을 프로젝트에 수동 추가 (의존성 있는 라이브러리들이 다수 있음)
OR
implementation 'com.google.firebase:firebase-admin:9.2.0'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.2.2'
[TestFcmNotification.java]
import java.io.FileInputStream;
import java.io.IOException;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
public class TestFcmNotification {
public static String json_file = "...\\res\\firebase-adminsdk.json";
// 메인 메서드
public static void main(String[] args) {
String token = null;
// Firebase 앱을 초기화
initializeFirebaseApp();
// notification을 발송
//pc firefox
token = "fdkIKreAEZ8i-XDD-WbAhq:APA91bG06N7F9gj1_...";
sendNotification(token, "FCM Firefox Message", "This is an FCM Firefox Message");
//pc edge
token = "c0Cae9s0m-FVSotbzr9mnF:APA91bGPEgB1V4e...";
sendNotification(token, "FCM Edge Message", "This is an FCM Edge Message");
//pc whale
token = "cBQhRiyHaKBoiOWhr9Wf8l:APA91bGmZOobkU...";
sendNotification(token, "FCM Whale Message", "This is an FCM Whale Message");
//pc chrome
token = "dx8Mftb9Avpznp2IgDQfzC:APA91bHRjnrZQMG...";
sendNotification(token, "FCM Chrome Message", "This is an FCM Chrome Message");
//mobile chrome
token = "dKXYcP8pcnvRg7qLsR8lbG:APA91bEA1s-G70n...";
sendNotification(token, "FCM Mobile Chrome Message", "This is an FCM Mobile Chrome Message");
//mobile default
token = "esR-hyLHS2cMKNRC9MJw68:APA91bEdJqUZQ...";
sendNotification(token, "FCM Mobile Default Message", "This is an FCM Mobile Default Message");
//mobile whale
token = "dB4913luC307KcP8JNbqe2:APA91bH1jTrazYp...";
sendNotification(token, "FCM Mobile Whale Message", "This is an FCM Mobile Whale Message");
//mobile edge
token = "d4aB0Xw4ZKVv3bdgMymnlf:APA91bFTsq7d...";
sendNotification(token, "FCM Mobile Edge Message", "This is an FCM Mobile Edge Message");
}
// Firebase 앱을 초기화하는 메서드
public static void initializeFirebaseApp() {
try {
// google-services.json 파일의 경로를 지정
FileInputStream serviceAccount = new FileInputStream(json_file);
// Firebase 옵션을 설정
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount)).build();
// Firebase 앱을 초기화
FirebaseApp.initializeApp(options);
} catch (IOException e) {
// 예외 처리
e.printStackTrace();
}
}
// notification을 발송하는 메서드
public static void sendNotification(String token, String title, String body) {
try {
// notification 객체를 생성
Notification notification = Notification.builder().setTitle(title).setBody(body).build();
// 메시지 객체를 생성
Message message = Message.builder().setNotification(notification).setToken(token).build();
// FirebaseMessaging 객체를 가져옴
FirebaseMessaging firebaseMessaging = FirebaseMessaging.getInstance();
// 메시지를 발송하고 결과를 받음
String response = firebaseMessaging.send(message);
// 결과를 콘솔에 출력
System.out.println("Successfully sent message: " + response);
} catch (FirebaseMessagingException e) {
// 예외 처리
e.printStackTrace();
}
}
}
로직 적용은 위와 같이 하면 되고 테스트 환경 설정은 sitelink1 의 내용을 참고하면 된다.
준비가 끝났다면 TestFcmNotification.java 를 실행해서 브라우저에 알림이 뜨는지 확인하면 된다.
참고로 위와 같은 셋팅으로도 edge(ver:119.0.2151.58)와 firefox(ver:120.0) 기준으로 최초 접속시에도 알림 권한 허용에 대한 프롬프트가 활성화 되지 않는다.
권한 허용 프롬프트 없이 사용자가 직접 브라우저의 알림 설정을 '허용'으로 변경해 줘야만 메세지 푸시가 정상 동작한다.
edge는 권한을 수동으로라도 변경하지 않으면 브라우저가 아래와 같이 오류 메세지를 출력하면서 권한을 아예 차단 시켜버린다.
"permission has been blocked as the user has ignored the permission prompt several times"
stackoverflow 에서 4개월전에 동일한 증상을 호소한 개발자도 아직까지 답변을 못받고 있는 상태이므로 해결을 포기했다. -> stackoverflow
그리고 아이폰의 모든 브라우저는 지원 불가다. (push event 가 발생하지 않음 -> mozilla, fcm이 아닌 web-push는 우회로 가능)
다음은 pc 와 mobile 의 각 브라우저들을 테스트한 결과이다.
1. PC
- Edge(알림 수동 설정함) : 브라우저를 닫은 상태에서 알림 수신 성공
- Firefox(알림 수동 설정함) : 브라우저를 활성화해야만 알림 수신 성공
- Chrome : 브라우저를 활성화해야만 알림 수신 성공
- Whale : 브라우저를 활성화해야만 알림 수신 성공
2. Mobile
- Default : 브라우저가 비활성화 상태에서도 수신 성공
- Edge : 토큰은 받았지만 알림을 전송해도 반응이 없음
- Firefox : 알림 수동 설정 불가
- Chrome : 브라우저가 비활성화 상태에서도 수신 성공
- Whale : 알림 허용창은 뜨지만 토큰을 받아오지 못함
브라우저별 분기 처리해야만 하나보다 (pwa 에서 크로스브라우징 작업을 해야할 줄이야...)
방어코드가 꽤 길어질 것 같다.
댓글 0
번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
---|---|---|---|---|
13 | FirebaseMessagingException: Requested entity was not found. | 황제낙엽 | 2024.01.12 | 1511 |
12 | (Copilot) Admin SDK Reference의 java 라이브러리를 이용하여 notification을 fcm에 전송하는 java 예제 | 황제낙엽 | 2023.11.28 | 205 |
11 | firebase.messaging().getToken() 함수와 pushManager.subscribe() 함수의 관계 | 황제낙엽 | 2023.11.26 | 275 |
10 | [FCM] FCM 으로 알림 전송 테스트 (spring boot + android + fcm rest) | 황제낙엽 | 2023.11.24 | 178 |
9 |
[FCM] Firebase Console 에서 메세지 보내기
![]() | 황제낙엽 | 2023.11.24 | 156 |
» |
(OMNIBUSCODE/FCM/WEB/JAVA) web push notification (web browser) 샘플
![]() | 황제낙엽 | 2023.11.23 | 146 |
7 | (Copilot) Notification Server 의 종류 | 황제낙엽 | 2023.11.23 | 141 |
6 | (OMNIBUSCODE/FCM/WEB/JAVA) web push notification (web browser) 구현 절차 [1] | 황제낙엽 | 2023.11.20 | 190 |
5 | 푸쉬 알림 개발 관련 레퍼런스 | 황제낙엽 | 2023.11.09 | 174 |
4 | [bard] web-push와 fcm 의 차이 | 황제낙엽 | 2023.11.08 | 224 |
3 | PWA 관련 링크 모음 | 황제낙엽 | 2023.11.06 | 165 |
2 | FCM (firebase cloud message) 사용하기 | 황제낙엽 | 2021.02.09 | 189 |
1 | FCM 과 GCM 차이 | 황제낙엽 | 2019.02.19 | 311 |