이전 포스팅에서는 PWA를 홈 화면에 추가하는 것까지 구현했다면 이번에는 모바일에서 서비스 페이지 진입 시 PWA 설치 유무를 확인하여 홈 화면 추가
를 유도하는 기능을 만들려고 한다.
웹의 url 표시줄에 있는 설치 아이콘
이나 모바일의 홈 화면에 추가
기능이 있는지 모르는 사용자를 위해 꼭 필요한 기능이라 생각해 만들게 됐다!
BeforeInstallPromptEvent란?
사용자가 웹 사이트를 모바일의 홈 화면에 "설치"하라는 메시지가 표시되기 전에 Windows object에서 발생하는 beforeinstallprompt 이벤트 - PWA 설치 가능할 때 발생하는 이벤트의 인터페이스이다.
BeforeInstallPromptEvent 참고 자료
<!--html-->
<button id="install" hidden>Install</button>
// javascript
let installPrompt = null;
const installButton = document.querySelector("#install");
window.addEventListener("beforeinstallprompt", (event) => {
event.preventDefault();
installPrompt = event;
installButton.removeAttribute("hidden");
});
installButton.addEventListener("click", async () => {
if (!installPrompt) {
return;
}
const result = await installPrompt.prompt();
console.log(`Install prompt was: ${result.outcome}`);
installPrompt = null;
installButton.setAttribute("hidden", "");
});
그런데! 모든 브라우저에서 지원하는 기능이 아닌 Safari
와 Firefox
에서는 지원하지 않아 이 두 브라우저에서는 웹이든 모바일이든 다른 배너를 보여주기로 했다.
// global.d.ts
declare global {
export interface WindowEventMap {
beforeinstallprompt: BeforeInstallPromptEvent;
}
}
export interface BeforeInstallPromptEvent extends Event {
readonly platforms: string[];
readonly userChoice: Promise<{
outcome: "accepted" | "dismissed";
platform: string;
}>;
prompt(): Promise<void>;
}
// _app.tsx
export default function App({ Component, pageProps }: AppProps) {
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | undefined>(undefined);
useEffect(() => {
const handleBeforeInstallPrompt = (e: BeforeInstallPromptEvent) => {
e.preventDefault()
setDeferredPrompt(e)
}
window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt)
if("serviceWorker" in navigator) {
navigator.serviceWorker
.register("/sw.js")
.then((reg) => console.log("sw worker registered", reg))
.catch(() => console.log("failed"))
}
return () => {
window.removeEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
}
}, [])
return (
<QueryClientProvider client={queryClient}>
<Component
{...pageProps}
deferredPrompt={deferredPrompt}
setDeferredPrompt={setDeferredPrompt}
/>
</QueryClientProvider>
)
}
가장 먼저 실행되는 컴포넌트인 _app에서 BeforeInstallPromptEvent에 대한 이벤트 리스너
와 컴포넌트가 언마운트될 때 이벤트 리스너를 제거하는 clean-up 함수
를 작성했다.
BeforeInstallPromptEvent 객체나 undefined 상태를 가진 deferredPrompt
와 setDeferredPrompt
를 props로 설치 버튼이 있는 컴포넌트에 전달될 수 있도록 했다.
export const checkUnsupportedBrowser = () => {
const userAgent = window.navigator.userAgent.toLowerCase()
return (
(userAgent.indexOf('safari') > -1 && userAgent.indexOf('chrome') <= -1 && userAgent.indexOf('chromium') <= -1 ) ||
(userAgent.indexOf('firefox') > -1 && userAgent.indexOf('seamonkey') <= -1)
);
}
원래는 /iPad|iPhone|iPod/.test()
정규식으로 확인하는 방식으로 하려고 했으나 웹으로 접속하는 경우도 고려해야했고 Browser_detection 에 따르면 BrowserName에 Chrome은 Chrome과 Safari를 함께 포함하는 등 문제가 있어 위와 같이
가 맞는지 확인하고 UnsupportedBrowser 여부를 return 하는 방법으로 해결했다.
Example:
크롬에서 위 userAgent를 출력하면 이렇게 chrome, safari가 함께 나온다.
mozilla/5.0 (macintosh; intel mac os x 10_15_7) applewebkit/537.36 (khtml, like gecko) chrome/122.0.0.0 safari/537.36
참고한 BrowserName 표는 다음과 같다.
export default function Home({ deferredPrompt, setDeferredPrompt } : { deferredPrompt: BeforeInstallPromptEvent, setDeferredPrompt : Dispatch<SetStateAction<BeforeInstallPromptEvent | undefined>>}) {
const promptAppInstall = async () => {
const isUnsupportedBrowser = checkUnsupportedBrowser();
if (isUnsupportedBrowser) {
alert("공유 아이콘 -> 홈 화면에 추가를 클릭해 앱으로 편리하게 이용해보세요!")
}
if (!isUnsupportedBrowser) {
if (deferredPrompt) {
deferredPrompt.prompt()
await deferredPrompt.userChoice
setDeferredPrompt(undefined)
} else {
alert("이미 저희 서비스를 설치해주셨어요!")
}
}
}
return (
// .. 코드 생략 ..
<button onClick={promptAppInstall}>홈 화면에 추가</button>
);
}
사용자가 서비스 페이지에 있는 홈 화면에 추가
버튼을 누르게 되면 먼저 3. 브라우저 구별 코드
에서 Safari 혹은 Firefox인지 확인하고 아래 표와 같이 실행되도록 했다.
Safari / Firefox | Chrome / Android | |
---|---|---|
설치 X | alert("공유 아이콘 -> ...") | 설치 프롬프트 창 띄워줌 |
설치 O | alert("공유 아이콘 -> ...") | alert("이미 저희 서비스...") |
로컬에서 확인하고 푸시했는데 Vercel 프리뷰에서는 안된다길래 약간 아찔했지만 main 브랜치로 머지하고 배포한걸로 확인해보니까 웹 & 모바일 잘 되는거 확인했다!
안드로이드 핸드폰이 없어서 팀원이 확인해줬기 때문에 스크린샷은 없다.
대신 Chrome 스크린샷 첨부..!
Safari / Firefox | Chrome / Android | |
---|---|---|
설치 X | ||
설치 O |