개발자
류준열

사파리에서 클립보드 복사 이슈

웹 api의 일부인 navigator.clipboard.writeText는 시스템 클립보드에 텍스트를 등록하는 기능을 제공하는데 Promise를 반환한다.

서버에서 받아은 링크를 복사하는 기능을 구현했는데, 사파리에서만 catch가 실행되었다.

const ReferralPage = () => {

  const copyInvitationUrl = async () => {
    const link = fetch(API_URL);
    await navigator.clipboard
      .writeText(link)
      .then(() => toast({ isSuccess: true, content: '링크를 복사했어요.' }))
      .catch(() => toast({ isSuccess: false, content: `실패했어요! ${navigator.userAgent}` }));
  };
 
 return (... 
        <InvitationUrlCopyButton onClick={CopyInvitationUrl}>
            초대링크 복사하기 <img src={HeartImg} alt="하트이미지" />
         </InvitationUrlCopyButton>
)}

사파리에서 복사기능 구현시 주의할 점

알아보니 사파리에서는 보안상의 이유로 유저 액션을 통해서만 navigator.clipboard.writeText가 동작하는데, 내가 작성한 코드에는 복사 전에 api call이 있었고 이를 보안상 이슈로 판단해 사파리 브라우저에서 자체적으로 catch를 실행시킨 것이다. (유저 클릭 액션 -> api call -> 복사)

  const CopyInvitationUrl = async () => {
    const link = fetch(API_URL); // api call

    await navigator.clipboard
      .writeText(link)
      .then(() => toast({ isSuccess: true, content: '링크를 복사했어요.' }))
      .catch(() => toast({ isSuccess: false, content: `실패했어요! ${navigator.userAgent}` }));
  };

그래서 일단 onClick에는 복사기능만 남겨두었다. (유저 클릭 액션 -> 복사)

const CopyInvitationUrl = async () => {
    // api call 삭제 
    
    await navigator.clipboard
      .writeText(link)
      .then(() => makeToast({ isSuccess: true, content: '링크를 복사했어요.' }))
      .catch(() => makeToast({ isSuccess: false, content: `으악 실패했어요! ${navigator.userAgent}` }));
  };

그럼 api call 어디서함?

처음에 시도했던 방식: useLayoutEffect

처음에는 api call을 useLayoutEffect안에 넣었다.
해당 api가 렌더링에 영향을 주지 않기 때문에 paint 전에 불러와도 되기 때문이다.

하지만 복사를 할지 안할지도 모르는데 무조건 api를 요청하는 방식은 맞지 않다고 생각했다.

두번째 방식: onMouseOver

계속 고민하다가 onMouseOver가 떠올랐다.

버튼 주변에 더 넓은 box를 만들고 그 위에 마우스를 올린다는 것은 버튼을 클릭할 의도가 다분하다는 것으로 판단하여 해당 box의 onMouseOver에 fetch를 넣어서 유저가 링크 복사 버튼 근처로 마우스를 이동시키면 미리 api를 요청하도록 했다.

설명

        <Wrapper onMouseOver = {()=>fetch(API_URL)}>
          <InvitationUrlCopyButton onClick={CopyInvitationUrl} onMouseOver = {()=>fetch(API_URL)}>
            초대링크 복사하기 <img src={HeartImg} alt="하트이미지" />
          </InvitationUrlCopyButton>
        </Wrapper>

정리

사파리에서는 보안상의 이유로 유저 액션 직후에만 navigator.clipboard.writeText를 성공시킨다.

  • asis
    • 클릭 -> api call -> navigator.clipboard.writeText(text)
  • tobe
    • 클릭 -> navigator.clipboard.writeText(text)
    • api call은 다른곳에서