✨ 들어가기
앞선 포스팅에서 PWA 기술에 대해 살펴보았다.
해당 기술에 대한 장점을 살려 지속적인 사용자 유입을 위해 Pic.me 서비스에 도입을 하게 되었다.
사실 chrome으로 구현하는 것은 이미 PWA에 대한 API가 대부분 제한되어 있지 않기에 단순히 service-worker를 등록해주기만 하면 구현이 가능했다. 하지만, 나 역시도 그렇고 대부분의 사람들이 아이폰을 사용하고 있었기에 우리는 safari 환경에서 이를 구현할 수 있게 만들어야 했다. 하지만 Push 알림에 대한 제한사항이 많아서 어려움을 겪었고, 해당 과정에서 알게 된 Safari + PWA 과정을 포스팅 해보고자 한다.
✅ Push 알림 구조
우선 Push 알림 구조에 대해 파악해보자.
클라이언트가 데이터를 요청하지 않아도
중앙 서버에서 전송 요청을 보내는 Pull 방식으로 진행된다.
주로 FCM, GCM, 애플의 APNs 등의 클라우드 메세징 서비스를 사용해 푸쉬 서버를 구축해놓고 사용한다.
📍 Application Server 와 Cloud Messaging Service
Application Server | Cloud Messaging Service |
푸쉬서버와 분리시켜 서비스를 위한 독립적인 서버로 구현 | Push 알림에 대한 기능에 몰입 가능한 구조 |
📍 진행 순서
1. 사용자가 웹/앱에 접근하여 클라우드 메세징 서비스에 등록을 요청
2. 요청 받은 사용자에 대한 유저 등록 정보를 제공
3. 유저는 해당 정보를 Application Server로 전달
4. 팔로우 , 댓글 등과 같은 이벤트를 사용하기 위해 유저 정보와 Push 메세지 내용을 클라우드 메세징 서비스로 전달
5. Application Server로부터 수신 정보를 확인하여 Push 알림을 진행
✅ PWA + Safari에서 구현하기
FCM, GCM은 safari환경에 지원되지 않는다...!!!
그렇다면 어떻게 PWA를 아이폰 사용자들에게 제공할 수 있을까..?
방법은 간단하다 서버에서 중간 api통신을 만들어 서버가 FCM에 접근하고 클라이언트는 이를 제어하는 pull 방식으로 push 알림에 대한 정보를 클라우드 메세징 서비스에 등록하게 만들면 된다.
이를 위해서 우리가 작업해야 할 내용은 총 2가지 이다.
💡 Web Notification 권한 확인하기
이를 위해서 웹페이지 외부에서 사용할 수 있는 Notification API를 사용해야 한다.
Notification.permission
위의 코드로 알람 권한을 확인할 수 있고 이에 대한 값은 총 3가지 이다.
- default 👉 사용자에게 아직 권한을 요구하기 전, 알람 표시되지 않음
- granted 👉 사용자에게 알람 표시를 요구했으며, 사용자가 허용한 상태
- denied 👉 사용자에게 알람 표시를 요구했으나, 사용자가 거부한 상태
granted 상태가 되어야만 Push 기능을 사용하는 PWA로 구현할 수 있다.
이러한 승인을 받기 위해 prompt창을 띄워야 하는데...!
🚨 safari는 사용자의 이벤트를 통해서만 해당 권한 창을 띄우는 것이 가능하다 🚨
회원가입시 이벤트 푸쉬 알림 체크 목록이 있는 것도 이러한 이유 때문이 아닐까하는 생각이 드는 문제였다.
해당 문제를 해결하기 위해, 알림 허용 팝업창을 만드는 방법을 적용하였다.
허용 버튼을 누르면 해당 어플리케이션의 알림 허용을 제어하는 핸들러를 실행하고, 거부 버튼을 누르면 현재 띄워져 있는 모달이 닫히는 구현로직을 사용하여 커스텀 모달을 만들었다.
아래의 코드는 허용 버튼을 눌렀을 때, 동작하는 핸들러 함수이다.
const handlePermission = async () => {
console.log('권한 요청 중...', Notification.permission);
// 알림 권한 받기 전
if (Notification.permission === 'default') {
// 요청하기
const permission = await Notification.requestPermission();
if (permission === 'granted') {
registerWorker().catch((err) => console.error(err));
} else {
alert('Please allow notifications.');
}
}
// 승인 - GRANTED
else if (Notification.permission === 'granted') {
registerWorker().catch((err) => console.error(err));
}
// 거부 - DENIED
else {
alert('알림 권한을 허용해주세요!');
}
};
여기서 default, granted 상태에 마주했을 때,
서비스 워커 등록 여부를 확인하고 클라우드 메세징 서비스에 유저의 정보를 구독하는 방법을 사용하였다.
import { client } from '../lib/axios';
export const registerWorker = async () => {
// (B1) 공유키
const publicKey = process.env.REACT_APP_PUBLIC_SUBSCRIBE_KEY;
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
// (B2) 서비스 워커 등록 및 확인
const registration = await navigator.serviceWorker.register(swUrl);
if (setNotificationPermission) {
setNotificationPermission('install');
}
if (registration.active) {
// (B3)서버에 구독하기
const subscription = await registration.pushManager.subscribe({
applicationServerKey: publicKey,
userVisibleOnly: true,
});
console.log(JSON.stringify(subscription));
await client
.post('alarm/register', subscription)
.then((res) => res.data)
.then((txt) => console.log(txt))
.catch((err) => console.error(err));
}
};
서비스 워커에 접근하기 위한 공유키를 서버로부터 제공받아 환경변수에 저장한다.
해당 키가 유효한 서비스 워커라면 서비스 워커를 등록하고 설치한다.
이후, 해당 서비스 워커가 활성화가 되는 단계에 도달하면 서버에 구독하는 작업을 통해 알림 기능을 허용한 사용자로 만들어진다.
💡 service-worker 정의하기
서비스 워커에 대한 정의는 프로젝트 src폴더 안에 만들어주어야 한다.
왜냐하면 build가 되었을때 루트 폴더로 들어가야 앞선 서비스 워커 확인 과정에서 접근하기 편리한 url로 만들어지기 때문이다.
그렇다면 어떤 내용으로 파일을 구성해야 할까?
총 4단계를 진행해야 한다.
1. ServiceWorker API를 사용할 수 있는 self를 정의
<reference lib="webworker" />
declare const self: ServiceWorkerGlobalScope;
2. ServiceWorker 가 activate 되면 현재 사용 가능한 클라이언트를 요청할 수 있는 함수를 실행
import { clientsClaim } from 'workbox-core';
clientsClaim();
3. PushMessage 타입을 정의
type PushMessage = {
title: string;
body: string;
};
4. install, activate, push 에 관한 eventListener를 구현
self.addEventListener('install', () => {
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
consoleMessage('ACTIVE');
});
self.addEventListener('push', (event: PushEvent) => {
const message = event.data?.json() as PushMessage;
event.waitUntil(
self.registration.showNotification(message.title, {
body: message.body,
icon: '/Pic.me_192.png',
}),
);
});
이렇게 service-worker.ts 파일을 구성하면 서비스 워커의 등록 절차에 따라 이벤트를 작동시키고 push 메세지도 지정한 type대로 받을 수 있게 된다.
🌈 마무리
이번 PWA를 safari 환경에서 구동시켜 보는 작업을 통해 아직까지는 PWA 기능이 모든 브라우저에 공통적으로 적용될 수 있는 데에는 한계가 있다는 것을 알게 되었다. 하지만 지속적인 사용자 유치와 앱개발의 진입에 어려움이 있을 때 사용하기에는 분명한 장점이 있는 기술로 느껴졌다.
'WEB > React' 카테고리의 다른 글
[ React ] useQuery 와 useMuataion를 활용한 기능개선 (0) | 2023.11.30 |
---|---|
[ 모던 리액트 : Deep Dive ] - 리액트 개발에서 반드시 알아야하는 JS (0) | 2023.11.28 |
[ 모던 리액트 : Deep Dive ] - 리액트에 대해 (1) | 2023.11.13 |
Intersection Observer API 를 활용한 무한스크롤 구현 (0) | 2023.07.03 |
Recoil (0) | 2022.07.03 |