반년 전, 해커톤 프로젝트에서 CRA로 만들고 firebase로 배포한 UNINSTAGRAM이라는 작은 익명 게시판 애플리케이션을 만들게 되었다. 그 당시에도 이미지 관련 CORS 이슈가 있어 크롬에서는 이미지가 제대로 나오지 않고 콘솔창이 더러워지는(...) 문제가 있었지만, 일단 다른 브라우저에서는 해당 이슈가 발생하지 않아 그냥 냅두고 있었던 문제인데... 계속 마음의 짐으로 남아있었다. 😓

그렇게 반년이 흘러 오늘이 되었고... 오늘 마침 딱히 할 일이 없어서... 수정하게 되었다.

이렇게 오늘 UNINSTAGRAM을 수정하는 과정에서 발생한 문제점과 그 해결 과정에 대해 기록해보려고 한다...

 

ERR_SSL_PROTOCOL_ERROR 발생

  • 증상
    • 타 사이트로부터 가져오는 아바타 이미지가 나타나지 않음. 크롬에서는 해당 이슈가 있었으나 파이어폭스에서는 정상적으로 표시됨. 콘솔창에 아래와 같은 에러메시지가 표시됨.
Mixed Contet: The page at 'https://{어쩌고}'
was loaded over HTTPS, but requested as
insecure XMLHttpRequest endpoint 'http://{저쩌고}'.
This request has been blocked;
the content must be served over HTTPS.
  • 원인
    • 기존에 아바타 이미지를 가져오던 사이트(http://turnyournameintoaface.com/)에서 SSL을 사용하고 있지 않아, 크롬 보안정책에 의해 이미지를 가져오는 요청이 막히게 됨.
  • 해결 방법
    • 해당 사이트에서 SSL을 사용하지 않는 이상 우리 쪽에서 문제를 근본적으로 해결할 수 있는 방법은 없어보여서, 결국 아바타 소스를 (눈물을 머금고) 포기하게 되었다.. 사용자가 입력한 닉네임에 따라 랜덤한 이미지를 가져오기 위해 해당 사이트의 이미지 생성 로직을 나름 연구(?)하고 적용시키느라 밤을 지새웠던... 그런 추억이 있지만.. 이젠 모두 주석이 되어버렸다.

      안녕 세기말 아바타 ... ㅠㅠ
       
    • 대신! cartoon-avatar라는 랜덤 아바타 생성 라이브러리로 교체를 하게 되었다. 아바타의 눈이 없는 게 기존의 세기말 아바타와 느낌이 비슷해서 마음에 들었다. 사용법은 엄청 간단하다. 문서에 친절하게 잘 나와있어서 그대로 따라했다.
      아바타 생성 메서드 toonavatar.generate_avatar() 를 호출하면 이미지 url을 반환하는데, 이때 아무런 인자를 전달하지 않으면 매번 다른 이미지를 반환하게 된다. 하지만 인자에 gender('male' | 'female')와 id(number) 값을 갖는 options 객체를 전달하게 되면 toonavatar.generate_avatar({ gender: 'female', id: 5 }) 그 값에 매칭되는 이미지를 반환하게 된다.
      우리는 기존과 마찬가지로, 동일한 닉네임에 동일한 아바타를 매칭시키기 위해 닉네임마다 동일한 id 값을 전달해야 했다. 그래서 사용자가 입력한 닉네임 값을 숫자로 변환하는 로직을 기존 변환 로직을 조금 변형하여 적용하게 되었다. 이 로직을 통해 만들어진 id 값이 라이브러리에서 정해진 id 값을 초과하더라도, 라이브러리에서 알아서 1부터 다시 카운트를 해주기 때문에 이 부분에 대한 추가 처리는 별도로 하지 않아도 되어서 너무나 편리했다.
      /* toonavatar id generator (2021.10.02 추가) */
      const genId = (nickname = '') => {
        // get each utf
        const charCodeArray = [...nickname].map(word => word.charCodeAt());
      
        const sum = charCodeArray.reduce((total, code) => {
          const str = code + '';
          return total + str[str.length - 1];
        }, '');
      
        return sum;
      };
      
      /* Custom Hook----------------------------------------------------------- */
      
      export default function useBitFaceState(input = '') {
        const [url, setUrl] = React.useState('');
      
        React.useEffect(() => {
          setUrl(() => {
            const id = genId(input);
            const src = toonavatar.generate_avatar({
              gender: id[0] % 2 ? 'female' : 'male',
              id: id.slice(1).length
                ? +id.slice(1) === 0 ? 1 : +id.slice(1)
                : 1,
            });
      
            return src;
          });
        }, [input]);
        return url;
      }

 

리액트에서 접근성 준수하면서 이모지(emoji) 사용하기

아바타 라이브러리를 사용하게 되면서 입력값에 따른 id 생성 로직을 손보다 보니, 아무래도 기존에 닉네임 글자수 제한이 없는 게 마음에 걸렸다. 혹시라도 1000자씩 입력하는 사용자가 있다면..? id 연산 과정도 걱정되고 레이아웃이 무너질 것도 걱정이 되었다. (물론 애초에 사용자가 없어서 그럴 일은 없겠지만서도 ㅠ) 그래서 일단 글자수를 제한하고, 닉네임을 입력하는 input 요소 아래에 다음과 같이 작은 안내 문구를 작성했다. 근데 예상치 못한 에러가 발생했다!

글자수 제한 안내 문구

 

  • 증상
    이모지 관련 접근성을 위반했다는 에러 메시지

    "이모지는 <span>으로 감싸야 하고, role="img" 속성이 필요하며 aria-label 또는 aria-labelledby 속성을 통해 설명을 제공해아 한다."

    * aria-label, aria-labelledby, aria-describedby 비교 (참고)
    • aria-labelledbyaria-labelaria-describedby 모두 현재 요소에 설명을 제공하는 attribute이다.
    • aria-label는 간결한 설명(string)을 '직접' 제공한다.
    • aria-labelledby'간결한' 내용을 참조(연결)하는 방식으로 설명한다.
    • aria-describedby attribute는 ID 값을 이용하여 '상세한' 내용을 참조(연결)하는 방식으로 설명한다.
    • aria-labelledby와 aria-label을 함께 선언할 경우, aria-label의 우선순위가 낮기 때문에 보조기기는 aria-labelledby를 설명한다.
  • 원인
    • 위 에러 메시지에 적힌 내용을 준수하지 않아서였다. (부끄럽지만 전혀 몰랐던 사실인데 에러 덕분에 알게 되어서 오히려 좋다~)
  • 해결 방법
    • 그냥 저기에 안내 되어있는대로 했다. ㅎ
      <small>
        닉네임은 최대 12자리까지 가능합니다.
        <span role="img" aria-label="heart">💙</span>
      </small>
    • 이러고 끝내면 재미없으니까 추가로 덧붙이자면 ... 이 포스트를 읽어보면 리액트 사용 시의 장점 중 하나인 '재사용성'을 살려서 더 효율적으로 처리할 수 있는 방법을 제시하고 있다. 위 방법도 괜찮은 방법이지만, 이모지를 자주 써야하는 프로젝트에선 이모지를 쓸 때마다 하나하나 <span>으로 감싸고 접근성 관련 속성들을 추가해야 하는 것이 귀찮은 일일 수 있다. 그래서 해당 포스트에서 제시한 방법은 다음과 같다.
      import React from 'react';
      
      const Emoji = props => (
          <span
              className="emoji"
              role="img"
              aria-label={props.label ? props.label : ""}
              aria-hidden={props.label ? "false" : "true"}
          >
              {props.symbol}
          </span>
      );
      export default Emoji;​
       
      • 요런 식으로 label과 symbol을 전달받는 Emoji 컴포넌트를 만들어두면 아래와 같이 사용할 수 있다.
        <Emoji symbol="🐑" label="sheep"/>
        // or
        <Emoji symbol="🐑"/>​
      • 물론 우리 프로젝트에선 이모지를 위 파란 하트 외에 딱히 쓸 일이 없어서 Emoji 컴포넌트를 따로 만들진 않았는데, 혹시라도 이모지를 자주 써야 하는 프로젝트가 있다면 확실히 유용할 것 같다.

        * aria-hidden : 보조기기의 접근 차단 여부를 결정하며, 값이 true일 경우 보조기기가 해당 콘텐츠를 무시함. (참고)

 

FirebaseError: [code=not-found] / project=undefined

  • 증상
    • "와~ 해결했다~"하고 firebase 재배포를 마친 순간, 사이트에 들어가보니 아래와 같은 에러가 뜨면서 포스트 목록이 불러와지지 않았다. 구글링해도 원인을 찾을 수가 없어서... 절망스러웠다... 게다가 project=undefined라니...? 해커톤 프로젝트와 연결이 되어있었으니 그간 배포를 해둔 사이트가 정상적으로 돌아가고 있었던 게 아닌가?
      code=not-found
  • 원인
    • 구글링해도 원인을 찾을 수 없던 이유: 너무 바보같은 실수라서 ㅋ
    • 다른 게 아니고, 집에서 사용하는 데스크탑에서 이 프로젝트 코드를 수정한 게 오늘이 처음이라 env 파일이 당연히 없었다. 클론할 때 그 부분에 대해서 미처 생각을 못하고 있었다. (아이고 참..) "project가 왜 undefined이지..?"하고 한참을 고민하면서 권한이 없어서 그런 건지 무엇때문인지 구글링도 하고 firebase 콘솔에서 이곳저곳 돌아다녀보고 하다가 문득 firebase 설정을 해둔 파일을 쳐다봤다.
      const firebaseConfig = {
        apiKey: process.env.REACT_APP_FB_API_KEY,
        authDomain: process.env.REACT_APP_FB_AUTH_DOMAIN,
        projectId: process.env.REACT_APP_FB_PROJECT_ID,
        storageBucket: process.env.REACT_APP_FB_STORAGE_BUCKET,
        messagingSenderId: process.env.REACT_APP_FB_MESSAGE_ID,
        appId: process.env.REACT_APP_FB_APP_ID,
        measurementId: process.env.REACT_APP_FB_MEASUREMENT_ID,
      };​
       그제서야 깨달았다.. "내가 env 파일을 만들어뒀던가...?" 그렇다. env 파일이 애초에 없어서 존재하지도 않는 환경변수들을 읽어오고 있었으니 값들이 모두 undefined여서 당연히 firestore에서 데이터를 가져올 수가 없었던 것이다...
  • 해결 방법
    • 이 포스팅 참고하여 env 파일 만들고 환경 변수 불러와서 제대로 설정 됐는지 확인한 후 재배포 했다.
    • 슬랙에 올려두었던 기존 env 파일을 그대로 가져와서 사용하려고 했으나, 그 파일에 있는 값들이 왜인지 모르게 현재 프로젝트의 값들과 달라 firebase 콘솔에 들어가 해당 값들을 다시 확인해야 했다. 우리의 경우와 같이 firebaseConfig에 들어가는 값들을 확인해봐야 한다면 아래의 경로를 통해 확인할 수 있다.

      1. firebase 콘솔 페이지에 들어간다.
      2. 좌측 메뉴바에서 톱니바퀴 모양의 '설정' 버튼을 누른다.
      firebase 콘솔 좌측 상단 메뉴의 설정 버튼

      3. '프로젝트 설정' 버튼을 클릭하여 해당 페이지로 이동한다.
      4. 일반 > 내 앱 > SDK 설정 및 구성 섹션에 보면 코드가 나와있는데 const firebaseConfig = { ... } 부분의 값들을 보면 된다. 이 값들은 민감한 정보이므로 github repo 같은 곳에 올라가면 안된다! 근데 이렇게 env 파일로 해놔도 빌드할 때 같이 포함돼서 올라가기 때문에 개발자 도구로 얼마든지 찾아볼 수 있다능.. ㅠ 이런 토이프로젝트에서 누가 알아도 딱히 써먹을 데 없어보이는(?) 이런 정보는 이 정도로 부실하게 처리한 것에 대해 어떻게든 정신승리할 수 있어도, 절대 공개되면 안 되는 값들(예: 유료 API key, secret key 등등)은 리액트 앱 단에서 관리하면 절대 안된다. node + express로 프록시 서버를 구축해서 처리해야 한다..!)

 

마치며...

솔직히 라이브러리만 추가하고 재배포할 심산이었는데 ... 뜻밖의 에러들을 발견해서 ... 너무 많은 걸 배워버렸다 .. env 파일 때문에 울면서 잠들 뻔 했는데 몇 시간 낭비하긴 했어도 결국에는 해결해서 다행이다. ㅎㅎ 솔직히 얻은 것(배움과 지식) 보다 잃은 것(시간)이 더 큰 거 같긴 한데 ㅎㅎ 그래도 이번 경험을 토대로 두번 다시 env 파일의 존재를 잊어버리는 실수는 하지 않을 것 같다~

 

 


[참고]

 

GitHub - lezhin/accessibility: 모두를 위한 설계. 레진 웹 접근성 가이드라인.

모두를 위한 설계. 레진 웹 접근성 가이드라인. Contribute to lezhin/accessibility development by creating an account on GitHub.

github.com

 

👋 ⚛️ — How to use emojis in React

Hey there! I released an NPM package called a11y-react-emoji with the Emoji component discussed in this article. You can check it out here…

medium.com

 

dotenv로 환경 변수 관리하기

Engineering Blog by Dale Seo

www.daleseo.com

 

[DB] 환경변수 사용하기 1 - dotenv

프로세스가 컴퓨터에서 동작하는 방식에 영향을 미치는 동적인 값들의 모임 위키피디아환경변수는 OS입장에서 해당 프로세스를 실행시키기 위하여 참조하는 변수이다.우리가 컴퓨터로 하는 모

velog.io

 

복사했습니다!