개발자
류준열

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

navigator.clipboard.writeText는 웹 api의 일부로 시스템 클립보드에 텍스트를 등록하는 기능을 제공한다. 비동기적으로 동작하면서 Promise를 반환한다.

다음과 같이 url 복사기능을 구현했는데, 사파리에서만 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 어디서함?

처음에는 api call을 useLayoutEffect안에 넣었다. 해당 api로 ui 가 변경되지 않기 때문에 paint 전에 불러와도 되기 때문이다.

하지만 복사를 할지 안할지도 모르는데 무조건 api를 요청하는 방식이 맘에 들지 않아서 계속 고민하다가 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은 다른곳에서