본문 바로가기
개발/브라우저와 네트워크

사파리에서 navigator.clipboard.writeText 안되는 경우

by 안뇽! 2023. 3. 20.
반응형

사파리에서 navigator.clipboard.writeText 안되는 경우

navigator.clipboard.writeText는 웹API의 일부로 시스템 클립보드에 텍스트를 쓰는 기능을 제공한다. 이는 사용자가 웹사이트에서 텍스트를 복사하거나 사용자를 대신해 텍스트를 클립보드에 저장하게 할 때 유용하다. 이는 비동기적으로 동작하며 Promise를 반환한다.

 

사파리 브라우저에서 navigator.clipboard.writeText가 동작하지 않는 경우가 있다. 사파리 브라우저에서는 보안상의 이유로 사용자의 상호작용을 통해서만 navigator.clipboard.writeText가 동작한다. 예를 들어 클릭 이벤트 핸들러와 같은 사용자의 직접적인 상호작용에 의해서만 호출된다.

 

위 내용은 결론부터 적은 것이고, 이제 이 교훈을 알게 된 과정을 소개한다.

사파리에서 navigator.clipboard.writeText 에러

const referralCode = await getReferralKey(); //api

const urlQueries = window.location.search ? `&${window.location.search.split('?')[1]}` : '';

//api response를 복사하려고 함
const link = `${window.location.origin}/referred?referralCode=${referralCode}${urlQueries}`;

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

사파리에서 navigatior.clipboard.writeText 안되는 경우

크롬으로는 잘 되는데 사파리에서는 계속 catch가 실행되었다.

 

대부분 블로그에서 가상의 textarea를 만들고 복사하고 document.execCommand('copy')를 쓰라는데 document.execCommand('copy')가 deprecate되서 작동하지 않았다. (뭐 이런글..)

 

계속 검색하다가 원인을 알게 되었는데, 사파리에 navigatior.clipboard가 없는것이 아니라 사파리의 보안정책이 엄격해서 api호출이후에 navigator.clipboard가 실행되면 무조건 catch로 보내는 것이었다.

 

사파리는 보안상의 이유로, 사용자의 입력을 필요로 하는 작업을 통해 사용자의 액션 후에 navigation.clipboard.writeText가 실행되도록 한다.

 

api response를 복사하기 위해서는 사용자 입력(온클릭) -> api 호출 -> response를 copy 하는 순서로 작동이 되어야 하는데, 이 때 사파리는 사용자의 액션으로 인한 복사가 아니라고 판단하고 catch()를 실행시킨다.

 

즉, 사파리에서 navigator.clipboard.write으로 복사를 하기 위해서는

사용자 입력(온클릭)전에 먼저 복사할 메시지를 뽑아놓고, 온클릭에서는 딱 복사만 해야한다.

 

내가 한 방법은 다음과 같다.

  1. useLayoutEffect로 먼저 text를 뽑아놓고 상태(에 저장했다.
  2. 온클릭했을때 바로 상태를 복사함
const [referralCode, setReferralCode] = useState('');


useLayoutEffect(() => {
    // 사파리 이슈때문에 onClick전에 key를 미리 만들어놓아야 함 : https://skasha.tistory.com/98
    if (profile) {
      const getKey = async () =>
        await getReferralKey().then(k => {
          setReferralCode(k);
        });
      getKey();
    }
  }, [profile]);
  
  
  const CopyInvitationUrl = async () => {
    try {
      console.log(referralCode);
      if (!profile) throw new Error('no login');

      const urlQueries = window.location.search ? `&${window.location.search.split('?')[1]}` : '';

      const link = `${window.location.origin}/referred?referralCode=${referralCode}${urlQueries}`;
      await navigator.clipboard
        .writeText(link)
        .then(() => makeToast({ isSuccess: true, content: '링크를 복사했어요.' }))
        .catch(() => makeToast({ isSuccess: false, content: `으악 실패했어요! ${navigator.userAgent}` }));
    } catch (e) {
      copyInvitationError(e);
    }
  };
  
  
  ...
  
  return  <InvitationUrlCopyButton onClick={CopyInvitationUrl}>
            초대링크 복사하기 <img src={HeartImg} alt="" />
          </InvitationUrlCopyButton>

참고 문서

이 글 아니었으면 해결 못했다. https://skasha.tistory.com/98

반응형