[Next.js] 2. PWA 설치 트리거

xoxristine·2024년 3월 15일
2

REINPUT

목록 보기
2/3

이전 포스팅에서는 PWA를 홈 화면에 추가하는 것까지 구현했다면 이번에는 모바일에서 서비스 페이지 진입 시 PWA 설치 유무를 확인하여 홈 화면 추가 를 유도하는 기능을 만들려고 한다.

무신사는 PWA가 아니긴한데 이런식으로 앱 설치 유도..한다

웹의 url 표시줄에 있는 설치 아이콘이나 모바일의 홈 화면에 추가 기능이 있는지 모르는 사용자를 위해 꼭 필요한 기능이라 생각해 만들게 됐다!


1. BeforeInstallPromptEvent

BeforeInstallPromptEvent란?
사용자가 웹 사이트를 모바일의 홈 화면에 "설치"하라는 메시지가 표시되기 전에 Windows object에서 발생하는 beforeinstallprompt 이벤트 - PWA 설치 가능할 때 발생하는 이벤트의 인터페이스이다.

BeforeInstallPromptEvent 참고 자료

1 . JS 예시

<!--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", "");
});

2. 브라우저 호환성 문제

그런데! 모든 브라우저에서 지원하는 기능이 아닌 SafariFirefox에서는 지원하지 않아 이 두 브라우저에서는 웹이든 모바일이든 다른 배너를 보여주기로 했다.


2. 구현 방법

1. 타입 선언

// 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>;
}

2. BeforeInstallPromptEvent 이벤트 리스너

// _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 상태를 가진 deferredPromptsetDeferredPrompt 를 props로 설치 버튼이 있는 컴포넌트에 전달될 수 있도록 했다.

3. 브라우저 구별 코드

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를 함께 포함하는 등 문제가 있어 위와 같이

  1. Safari 일 때 - Chrome | Chromium 절대 포함 X
  2. Firefox 일 때 - Seamonkey 절대 포함 X

가 맞는지 확인하고 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 표는 다음과 같다.

4. 홈 화면에 추가 버튼

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 / FirefoxChrome / Android
설치 Xalert("공유 아이콘 -> ...")설치 프롬프트 창 띄워줌
설치 Oalert("공유 아이콘 -> ...")alert("이미 저희 서비스...")

3. 결과

로컬에서 확인하고 푸시했는데 Vercel 프리뷰에서는 안된다길래 약간 아찔했지만 main 브랜치로 머지하고 배포한걸로 확인해보니까 웹 & 모바일 잘 되는거 확인했다!
안드로이드 핸드폰이 없어서 팀원이 확인해줬기 때문에 스크린샷은 없다.
대신 Chrome 스크린샷 첨부..!

Safari / FirefoxChrome / Android
설치 X
설치 O
profile
🔥🦊

0개의 댓글