[TIL] iframe과 데이터 통신하기

minami·2025년 3월 30일

일개미

목록 보기
3/13
post-thumbnail

얼마 전에 회사에서 업무 중에 HTML로 만들어진 약관 파일의 일부 내용을 동적으로 처리해야 할 일이 생겼다.
현재 약관 내용은 회사 서비스의 상황에 따라서 추후 업데이트가 될 수 있어서 따로 버전 관리가 필요하기 때문에 GCS에 HTML 파일을 따로 업로드하여 사용 중이다.
그래서 약관을 보여주어야 하는 페이지에서는 약관 파일을 iframe으로 삽입하여 보여주고 있었기 때문에 약관 내용을 동적으로 처리하려면 iframe 내부로 데이터를 전달해야 했다.

그런데 벌써 햇수로는 5년차, 만으로는 4년차인데 iframe이랑 데이터 통신을 해본 적이 없는 나... 어쩌면 개발자 인생 헛살았을지도?

iframe 내부에 삽입되는 HTML 파일 수정

1. 문자열 데이터를 삽입할 요소에 id 부여하기

우선 간단하게 약관 HTML 파일부터 수정하기로 했다.
데이터가 들어갈 부분을 빈 칸으로 만들고, 그 빈 칸 요소에 id값을 부여해서 document.getElementById()를 활용하여 해당 id를 가진 요소 내부에 innerText로 문자열 데이터를 설정할 수 있을 것 같았다.
그래서 아래처럼 우선 빈 요소에 id를 부여했다.

...
<td>
  <p id='terms-data'></p>
</td>
...

2. 헤더에 script 추가하기

이제 앞서 문자열 데이터가 들어갈 자리를 찾기 위해서 id를 부여했으니 해당 id를 가진 요소를 찾아내서 문자열 데이터를 세팅할 수 있도록 HTML 파일의 헤더에 스크립트를 추가해야 한다.
요거 하느라 Vanilla JS를 너무 오랜만에 해봤다.

...
<script>
  const termsDataElement = document.getElementById('terms-data');
  termsDataElement.innerText = '문자열 데이터';
</script>
...

우선 로컬에서 봤을 때는 당연히 로컬에서 수정한 HTML 파일 경로를 지정하여 iframe에 삽입했기 때문에 임의로 넣은 "문자열 데이터"가 잘 보이는 것은 확인이 되었다.
그럼 이제 진짜 문제는 부모가 자식한테 내리사랑을 어떻게 실천해야 하냐이다.

부모 윈도우에서 iframe으로의 데이터 전달

1. 부모 윈도우에서 postMessage()로 iframe과 데이터 통신하기

window.postMessage()

작년 말, 우리 회사에서는 웹으로만 운영하던 서비스를 앱으로 말아서 출시했다. 팀에 앱 개발자가 없는 데다가 앱 개발 경험이 있는 사람도 없다 보니 처음에는 노코드 툴을 썼는데, 아무래도 기능을 계속 추가로 개발하고 고도화를 하면서 네이티브와 통신이 필요한 부분이 늘어나다 보니 노코드 툴 업체에 매번 비용을 주면서 수정 요청을 하기가 애매했다. 물론 비용 쓰는 회사 입장에서^^
그래서 결국 채찍피티의 도움을 받아 개발팀 자체적으로 React Native로 웹을 앱으로 말아서 앱을 재출시했는데, 이때 웹뷰와 네이티브 간 통신을 위해서 사용했던 것이 바로 postMessage()였다.
서론이 좀 길다

그래서 postMessage()가 무엇인가?

MDN 문서에 따르면 postMessage()는 서로 다른 윈도우 객체 사이에 안전하게 통신을 할 수 있게 해주는 메서드이다.

즉, 어떤 페이지와 팝업창 사이에 통신을 해야 할 때나 페이지와 그 페이지의 iframe 내부 사이에 통신을 할 때 사용할 수 있다.
postMessage()가 앱 개발할 때만 사용하는 것이 아니라 부모 윈도우에서 iframe 내부로 데이터를 전달해야 하는 바로 지금도! 이 시점에도! 힘차게 등장해주어야 했던 것이다.

그래서 부모 페이지에 useEffect()를 추가하여 iframe 내부로 postMessage()로 데이터를 전달할 수 있도록 코드를 작성했다.

...
export default function ParentPage() {
  ...
  const iframeRef = useRef<HTMLIframeElement>();
  const { data } = useQuery(...);
  
  useEffect(() => {
    if (iframeRef.current) {
      iframeRef.current.onload = () => {
        iframeRef.current?.contentWindow?.postMessage(
          JSON.stringify({
            event: 'view-terms',
            payload: data
          })
        );
      }
    }
  }, [data]);
  ...
};
...

참고로 부모가 iframe 내부에 있는 윈도우 객체에 접근하려고 할 때에는 window객체를 반환해주는 contentWindow를 사용해야 한다. 그 외에 contentDocument라는 것도 있는데, contentDocument는 이름에서 알 수 있듯이 document를 반환한다. window객체가 document객체보다 더 상위에 있는 객체인 것은 다 알고 계시쥬?

(선택사항) 2. Same-Origin 정책 맞춰주기

클라이언트-서버 통신을 할 때에도 중요한 게 바로 이 동일 출처 정책이다. 클라이언트와 서버의 도메인이 다르면 보안을 위해서 브라우저단에서 통신을 막아버린다. 그래서 서버에서 해당 설정을 맞춰주어야 하는데 설정이 제대로 되지 않아서 API 통신을 할 때 CORS에러가 뜨는 경험을 우리 개발자라면 한 번씩은 다 해보셨겠죠?
동일 출처 정책과 CORS에 대해서 더 자세한 건 인터넷에 좋은 자료가 많으니 찾아보도록 하자. 그것도 찾아보기 귀찮다면 채찍피티에게 채찍을 휘둘러보세요.😉

부모와 iframe도 만약 도메인이 다르다면 부모가 아무리 postMessage()를 해보아도 iframe은 묵묵부답일 것이다.
바로 나처럼 iframe으로 불러오는 파일은 GCS에 올라가 있는데, 부모 페이지는 우리 서비스 도메인에 올라가 있는 경우가 해당된다.

이럴 때에는 postMessage()의 옵션을 활용하면 된다.

MDN 문서에서 설명하는 postMessage()의 문법: targetWindow.postMessage(message, targetOrigin, [transfer]);

postMessage()는 두 번째 인자로 타겟 오리진을 받고 있다. 그 말은, 오리진을 설정해서 안전하게 통신이 이루어지도록 다 알아서 해준다는 말이다.

따라서 아래처럼 두 번째 인자로 오리진 인자를 넘겨주면 iframe 내부로 안전하게 데이터를 전달할 수 있다.
참고로 이때 오리진 주소 인자로 "*"를 작성하면 모든 출처에 대해 허용할 수 있다. 그런데 이 경우에는 메시지 이벤트를 수신하는 쪽에서 안전하게 오리진 주소를 걸러내는 처리를 해주는 것이 보안상 좋다.

...
export default function ParentPage() {
  ...
  const iframeRef = useRef<HTMLIframeElement>();
  const { data } = useQuery(...);
  
  useEffect(() => {
    if (iframeRef.current) {
      iframeRef.current.onload = () => {
        iframeRef.current?.contentWindow?.postMessage(
          JSON.stringify({
            event: 'view-terms',
            payload: data
          }),
          '오리진 주소'
        );
      }
    }
  }, [data]);
  ...
};
...

iframe에서 메시지 이벤트 수신 설정

1. iframe의 script 이벤트 수정하기

이제 부모 페이지에서 iframe으로 이벤트를 보내는 것까지 완료되었으니 iframe 내부에서 메시지를 수신할 수 있도록 스크립트를 수정해보자.

postMessage()로 전송된 메시지 이벤트는 이벤트 리스너를 통해서 수신할 수 있다.
그리고 이벤트는 한 번만 실행하면 되기 때문에 이벤트 리스너를 해제하는 코드 작성도 잊으면 안 된다.

...
<script>
  window.addEventListner('message', function setTermsData(e) {
    if (!e.data) return;
    
    const data = JSON.parse(e.data);
    const { event, payload } = data;
  
    if (event === 'view-terms') {
      const termsDataElement = document.getElementById('terms-data;);
      termsDataElement.innerText = payload;
    }
  
    window.removeEventListener('message', setTermsData);
  });
  ;
</script>
...

이렇게 해서 부모와 iframe 간의 데이터 통신 해결! 짝짝짝👏

profile
함께 나아가는 개발자💪

0개의 댓글