치춘짱베리굿나이스

Web Share API와 공유 기능 만들기 본문

Javascript + Typescript/기타

Web Share API와 공유 기능 만들기

치춘 2023. 6. 25. 19:21

Web Share API

2차로 갈만한 가게를 추천해주는 웹앱 개발에 참여하면서, 특정 장소나 장소 모음집 (컬렉션) 을 다른 사람에게 공유해줄 수 있는 기능을 만들 일이 생겼다

스마트폰에서 공유 버튼을 누르면 대부분 같은 UI의 팝업이 뜨는데, 이걸 보니 공유 기능이 각자 독자적으로 개발된 것이 아니라 자바스크립트 또는 HTML 등에서 제공해주는 특정 API를 사용하겠구나 싶었고, 실제로도 그랬다

이 API를 사용해서 어떻게 링크 공유를 해줄 지 알아보자

설명

MDN에서의 설명 번역

The Web Share API allows a site to share text, links, files, and other content to user-selected share targets, utilizing the sharing mechanisms of the underlying operating system. These share targets typically include the system clipboard, email, contacts or messaging applications, and Bluetooth or Wi-Fi channels.

The API has just two methods. The navigator.canShare() method may be used to first validate whether some data is "shareable", prior to passing it to navigator.share() for sending.

The navigator.share() method invokes the native sharing mechanism of the underlying operating system and passes the specified data. It requires transient activation, and hence must be triggered off a UI event like a button click. Further, the method must specify valid data that is supported for sharing by the native implementation.

The Web Share API is gated by the web-share Permissions Policy. If the policy is supported but has not been granted, both methods will indicate that the data is not shareable.

Web Share API는 기본 운영 체제의 공유 매커니즘을 사용하여, 웹 사이트로 하여금 텍스트, 링크, 파일 등 여러 컨텐츠를 사용자가 선택한 공유 대상과 공유할 수 있도록 도와줍니다. 이러한 공유 대상에는 시스템 클립보드, 이메일, 연락처, 메시지 어플리케이션, 블루투스와 Wi-Fi 채널 등이 포함됩니다.

 

본 API는 navigator.canShare()navigator.share() 단 두 종류의 메서드로 구성되어 있습니다.

navigator.canShare()는 각 데이터가 “공유 가능한지” 여부를 검사하는 데에 사용되며, 여기서 검사한 데이터는 navigator.share() 메서드로 넘어가 실제로 공유가 이루어집니다.

 

navigator.share() 메서드는 기본 운영체제의 네이티브 공유 매커니즘을 실행시키고, 데이터를 전달합니다. 공유 매커니즘은 일시적으로 활성화되어야 하므로, 버튼 클릭과 같은 UI 이벤트를 통해 트리거되어야 합니다. 더 나아가, 이 메서드는 네이티브 구현에 의해 공유가 지원되는 유효한 데이터를 지정하여야 합니다.

 

Web Share API는 web-share Permissions 정책에 의해 통제됩니다. 만약 정책이 지원되지만 허가되지 않은 경우, 두 메서드 모두 데이터가 공유 불가능함을 나타냅니다.

간단한 설명

navigator.canShare();
navigator.share();

Web Share API는 위의 두 메서드로 구성된 간단한 API로, 운영체제에서 공식적으로 지원하는 공유 매커니즘을 깨워서 데이터를 전달하는 역할을 한다

navigator.canShare() 는 데이터가 공유가능한지 여부를 판별하고, 실질적으로 데이터를 전달하는 주체는 navigator.share() 가 되는 것

브라우저 밖의 무언가 (운영 체제에 탑재된 매커니즘) 를 일시적으로 실행시키는 역할을 하므로, 본 메서드들은 웹 이벤트에 의해 호출되어야 한다

 

위와 같이 스마트폰 (좌측 아이폰, 우측 갤럭시 노트) 에서 기본적으로 등장하는 팝업이 운영체제에서 네이티브하게 지원하는 공유 매커니즘이다

navigator.share() 는 이 매커니즘으로 어떠한 데이터를 전달하고 매커니즘을 실행시켜주는 역할을 한다

지원되는 환경

브라우저

  • 크롬 (윈도우, 크롬OS 한정)
  • 엣지
  • 사파리
  • 모바일 크롬
  • 모바일 사파리
  • 삼성 인터넷
  • 모바일 오페라
  • 모바일 파이어폭스

놀랍게도 대부분의 브라우저에서 지원을 안 한다

모바일 환경에서는 메이저한 브라우저에선 (크롬, 사파리, 삼성 인터넷, 오페라 등) 지원을 해 주지만 PC 환경에선 크롬은 크롬OS에서만 지원하므로 사실상 엣지와 사파리밖에 없다

모바일 환경에서 주로 사용될 앱의 경우에만 이 기능을 추가하고, 후술할 예외 처리를 통해 지원되지 않는 환경에서도 비슷한 동작을 하게끔 돕는 것이 좋겠다

환경

HTTPS 환경에서만 동작하므로, HTTP에서는 포기하자

 

또한 Web Workers (웹 멀티스레딩) 와도 같이 사용할 수 없다고 한다

사용 방법

공유 버튼 만들기

export default function ShareButton({ size = 24 }: Props) {
  return (
    <button type="button" className={s.share}>
      <ShareIcon width={size} height={size} />
    </button>
  );
}

엄청나게 간단한 버튼을 하나 만들었다

아직 클릭하면 어떠한 동작도 하지 않는다

간단한 onClick 핸들러 만들기

export default function ShareButton({ size = 24 }: Props) {
    function handleClickShare() {
        // Logic
    }

  return (
    <button type="button" className={s.share} onClick={handleClickShare}>
      <ShareIcon width={size} height={size} />
    </button>
  );
}

간단하게 핸들러 하나를 추가해서 버튼에 붙여주자

Web Share API 지원되는 지 확인하기

function handleClickShare() {
    if (navigator.share) {
      console.log('web share supported');
    } else {
      console.log('web share not supported');
    }
  }

코드 입력기 상에서 이 빨간 줄에 속으면 안된다!!!! 브라우저에 따라 항상 true를 반환하지 않기 때문이다!!!

지원하지 않는 브라우저에서는 navigator.share가 아예 존재하지 않기 때문에 조건문에 따라 분기된다

 

맥북에서 사용하는 사파리는 본 API를 지원하므로 web share supported가 출력된다

 

구글 크롬에서는 지원이 안 되므로 web share not supported가 출력된다

버튼에 공유할 데이터 넘겨주기

interface Props {
  sharedTitle: string;
  sharedUrl: string;
  sharedText?: string;
  size?: number;
}

export default function ShareButton({
  sharedTitle,
  sharedUrl,
  sharedText,
  size = 24,
}: Props) {

    ...
}

navigator.share() 가 보낼 수 있는 정보로는

  • title
  • url
  • text
  • files

네 종류가 있다

우리는 장소 모음집의 제목 (title), 모음집 링크 (url) 를 기본값으로 보내고, text는 컴포넌트에 따라 다르게 보낼 예정이므로 옵션값으로 설정하였다

navigator.canShare를 통해 보낼 수 있는 데이터인지 확인하기

function handleClickShare() {
    if (navigator.share) {
        console.log(
            navigator.canShare({
                title: sharedTitle,
                text: sharedText,
                url: sharedUrl,
            })
        );
    } else {
        console.log('본 기능은 현재 브라우저에서 지원되지 않습니다');
    }
}

navigator.canShare() 메서드를 이용하여 지금의 데이터가 전송가능한지 확인해보자

본 메서드는 boolean 값을 반환하므로, 전송 가능한 데이터라면 true를, 아니면 false를 출력할 것이다

여러 테스트를 해 본 결과,

  • title, text, url, files 중 하나라도 있으면 true
  • title, text, url에 빈 문자열 (’’) 이 있어도 true
  • url에 이상한 값 (asdf 등) 이 들어있어도 true
  • files만 존재할 때, files에 빈 배열이 있으면 false
  • title, text, url, files 모두 값이 없을 경우 false

결국 위의 네 값중 하나라도 정상적인 값이 들어와 있을 경우 데이터를 보낼 수 있다는 것

우리는 title, url을 필수값으로 사용하고 files는 사용하지 않을 것이므로 navigator.canShare() 는 봉인하도록 하자

navigator.share를 통해 데이터 전달하기

function handleClickShare() {
    if (navigator.share) {
        navigator
            .share({
                title: sharedTitle,
                text: sharedText,
                url: sharedUrl,
            })
            .then(() => {
                console.log('shared complete');
        })
        .catch(console.error);
    } else {
        console.log('본 기능은 현재 브라우저에서 지원되지 않습니다');
    }
}

navigator.canShare는 봉인하고 navigator.share를 꺼냈다

아까 navigator.canShare에 넣어주었던 데이터를 인자로 똑같이 넣어주면 된다

특이할만한 점은 프라미스를 반환하기 때문에 then, catch로 프라미스 체이닝이 가능하다는 점인데, 아마 외부 매커니즘 (네이티브 공유 매커니즘) 을 호출하기 때문에 비동기로 처리되는 듯하다

정상적으로 공유되었을 경우 then, 실패하였을 경우 catch로 넘어가므로 적절히 에러 핸들링을 해 주면 된다

Web Share API 가 지원되지 않는 브라우저는…

function handleClickShare() {
    if (navigator.share) {
        navigator
            .share({
                title: sharedTitle,
                text: sharedText,
                url: sharedUrl,
            })
            .then(() => {
                console.log('shared complete');
        })
        .catch(console.error);
    } else {
        navigator.clipboard.writeText(sharedUrl);
    }
}

간단하게 sharedUrl을 클립보드로 복사해드렸습니다 ^^

이외에도 폴백 컴포넌트를 렌더링하거나, 그냥 에러 alert를 띄워주거나 (…) 하는 방법이 있다

버튼 고도화

import { useEffect, useState } from 'react';

import { ShareIcon } from '../Icons';

import s from './ShareButton.module.scss';

interface Props {
  sharedTitle: string;
  sharedUrl: string;
  sharedText?: string;
  size?: number;
}

const DEFAULT = 0;
const IS_SHARED = 1;
const IS_COPIED = 2;

export default function ShareButton({
  sharedTitle,
  sharedUrl,
  sharedText,
  size = 24,
}: Props) {
  const [shareStatus, setShareStatus] = useState(DEFAULT);

  useEffect(() => {
    if (shareStatus === IS_SHARED || shareStatus === IS_COPIED) {
      setTimeout(() => {
        setShareStatus(DEFAULT); // 1초 뒤, DEFAULT 상태로 복귀
      }, 1000);
    }
  }, [shareStatus]);

  function handleClickShare() {
    if (shareStatus === IS_SHARED || shareStatus === IS_COPIED) return;
    if (navigator.share) {
      navigator
        .share({
          title: sharedTitle,
          text: sharedText,
          url: sharedUrl,
        })
        .then(() => {
          setShareStatus(IS_SHARED); // 공유 완료하면, IS_SHARED 상태로 변경
        })
        .catch(console.error);
    } else {
      navigator.clipboard.writeText(sharedUrl).then(() => {
        setShareStatus(IS_COPIED); // 복사 완료하면, IS_COPIED 상태로 변경
      });
    }
  }

  return (
    <button type="button" className={s.share} onClick={handleClickShare}>
      {shareStatus === IS_SHARED && <span>Shared!</span>}
      {shareStatus === IS_COPIED && <span>Copied!</span>}
      <ShareIcon width={size} height={size} />
    </button>
  );
}

공유가 잘 되었는지, 복사가 잘 되었는지 여부를 체크하기 위해 1초동안 Shared! 또는 Copied! 를 출력해주는 형태로 버튼을 개조해 봤다

작동 방식은 다음과 같다

  1. 공유 버튼을 누른다
    • navigator.share 사용 불가능한 브라우저라면
      1. URL을 클립보드에 복사한다
      2. 클립보드 복사가 완료되면 then 문으로 넘어가며, shareStatusIS_COPIED로 변경한다
    • navigator.share 사용가능한 브라우저라면
      1. OS의 네이티브 공유 기능을 활성화한다
      2. 공유 기능을 이용하여 다른 디바이스 또는 앱 등에 공유한다
      3. 공유가 종료되면 then 문으로 넘어가며, shareStatusIS_SHARED로 변경한다
  2. useEffectshareStatus 변경이 감지되고, setTimeout이 태스크 큐로 넘어간다
  3. shareStatus에 따라 아이콘 왼쪽 span에 문자열 (Shared! 또는 Copied!) 이 출력된다
  4. 1초 뒤, setTimeout에 의해 shareStatusDEFAULT로 돌아온다

좌측은 navigator.share를 사용할 수 없는 구글 크롬 (macOS 환경), 우측은 navigator.share 사용가능한 사파리에서의 동작이다

스타일 적용을 아직 안 해서 똥같은 align 똥같은 글자색이긴 하지만… 동작은 잘 되므로 만족한다


참고 자료

https://caniuse.com/web-share

https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/canShare

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share

'Javascript + Typescript > 기타' 카테고리의 다른 글

indexedDB  (0) 2023.07.08
localStorage / sessionStorage  (0) 2022.05.13
e.currentTarget과 e.target의 차이점  (0) 2022.05.04
Comments