브라우저 탭 사이에 데이터 공유하기
프론트 작업을 하면서 A태그의 target:_blank
를 이용해서 새 탭을 열어서 작업을 하는 과정이 있었다.
기존에 열고 있던 페이지가 (A) 새로운 탭이 (B)라고 했을 때, A와 B는 동일한 오리진으로 로컬 스토리지를 공유할 수 있었다.
현재 로컬 스토리지에서 Access Token을 관리하는 로직으로 프로젝트를 만들고 있다.
그래서 처음 진입을 하는 과정에서는 액세스 토큰을 사용할 때 문제가 없었다. 하지만 B에서 작업을 하던 중 Access Token의 만료로 다시 재발급을 받아 로컬 스토리지를 바꿨을 때 A에서는 아직 기존에 있는 Access Token을 들고 있었고 어떠한 작업을 할 때 Access Token이 필요하지만 과거의 Access Token을 가지고 있었기 때문에 에러를 발생하는 문제가 발생했다.
이를 해결하기 위해서 B페이지에서 새로운 token으로 갱신을 할 때 기존에 있는 페이지에서도 바꿔주면 되지 않을까?라는 생각을 했고 그러기 위해서 탭 간 통신을 할 수 있는 과정이 필요했다.
위의 상황은 내가 겪을 상황이지만 정리하자면
내 애플리케이션의 상태를 탭 사이에 공유해야 한다.
방법
-
- Local Storage Event
- 기본적으로 로컬 스토리지가 변하는 것에 대해서 이벤트를 감지할 수 있다고 있다. 만약에 단순히 로컬 스토리지만 관리한다고 했을 때는 이것을 써도 좋을 것 같다.
- window.postMessage
- window 객체 안에 있는 postMessage를 이용하는 방법도 있다. 다른 탭이나 윈도우, ifame 간에 메시지를 전달할 수 있다.
- 서로 다른 origin에서도 가능
- service worker
- 웹의 백그라운드 작업을 처리하는 데 사용되고, 푸시 알림, 네트워크 요청 캐시 등의 기능을 제공
- 탭 간의 통신보다는 오프라인 경험, 데이터 캐싱, 백그라운드 동기화에 적합
- serviceworker.js를 만들어 관리해야 한다.
- BroadCast Channel API
- 같은 Origin을 가진 다양한 브라우저 컨텍스트 사이에서 간단하게 데이터 교환을 할 수 있는 API
- 동일한 Origin 내에서 데이터 공유 및 실시간 커뮤니케이션에 이상적이다.
- Local Storage Event
선택
- 단순 로컬 스토리지뿐 아니라 메시지를 주고받을 수 있다, 추후 어떠한 상황이 있을지 아직 모른다는 점 (확장성)
- 브라우저 탭 간에 통신이 되고 그 데이터를 송수신한다는 점
- 같은 오리진이라는 점
- 간단하게 사용할 수 있어야 한다는 점
위의 세 가지를 고려해서 broadcast channel API
를 선택하게 되었다.
BroadCast Channel API
BroadCast Channel API는 같은 오리진에서 사용할 수 있다는 점으로 송수신자를 별도로 표기하지 않아도 된다. 마지막까지 봤었던 window.postMessage는 다른 오리진간에도 가능하기 때문에 보안을 위해서 메시지를 보낸 출처를 확인하는 과정이 있었다.
postMessage 예시
// 송신자
const childWindow = window.open('https://example.com/child-page.html');
// 메시지 전송
childWindow.postMessage('데이터', 'https://example.com');
//수신자
// 메시지 수신 리스너 설정
window.addEventListener('message', (event) => {
// 보안을 위해 메시지를 보낸 출처 확인
if (event.origin !== 'https://example.com') return;
console.log('Received message:', event.data);
});
반면에 broadcast channel API는 동일 오리진이기 때문에 다른 검증과정은 없고 인스턴스를 통해서 송수신을 했다.
Broadcast Channel API 예시
//송신자
const channel = new BroadcastChannel('채널이름')
channel.postMessage('데이터')
//수신자
const channel = new BroadcastChannel('채널이름');
// 메시지 수신 리스너 설정
channel.onmessage = (event) => { console.log('Received message:', event.data); };
조금 더 간단하게 쓸 수 있었고 다른 여러 창이 열려있는 상황에서도 동기화를 시킬 수 있다고 생각했고, 그래서 BroadCast Channel API를 이용하기로 했다.
BroadCast Channel API는 브라우저 기반 API이기 때문에 클라이언트 사이드에서만 사용할 수 있다.
어떻게 사용했는가?
브라우저 기반 API이므로 클라이언트 사이드에서 써야 한다. 우리의 프로젝트에서 어디에서나 작동하는 client side 컴포넌트는 root에 react-qeury(tanstack-query)를 세팅한 부분이다.
그래서 react-query를 세팅한 컴포넌트에서 수신을 하면 recoil을 통해서 로컬스토리지를 변경하는 로직을 추가하였다.
//rootwrap.tsx
const channel = new BroadcastChannel('bc')
channel.onmessage = (e) => {
if (e.data) {
setAccessToken(e.data) // recoil
}
}
기본적으로 우리 프로젝트에서 토큰이 만료되면 토큰이 만료됐다는 에러를 던지고 그걸 브라우저에서 감지하게 되면 재발급을 한 후 새로운 토큰을 이용해서 재요청을 진행한다.
그래서 토큰을 재발급받는 과정 뒤에 postMessage를 사용하였다.
const sendMessage = (message: string) => {
if (typeof window !== 'undefined') {
const channel = new BroadcastChannel('bc')
channel.postMessage(message)
}
}
// 토큰 발급 받은 후
sendMessage(accesstoken)
이러한 과정을 통해서 동일한 오리진의 다른 탭에서 토큰을 재발급받더라도 토큰 받을 후 로직에 sendMessage를 통해서 모든 탭의 토큰에 최신화를 시켜줄 수 있는 과정을 만들었다.
Broadcast Channel API 원리
일반적인 BroadcastChannel의 원리는 크게 3단계이다.
- 채널 생성 및 관리
- 메시지 큐잉 및 전달
- 이벤트 전파
1. 채널 생성 및 관리
- 브라우저는 각각의
BroadcastChannel
인스턴스에 대한 참조를 관리한다. 주로 브라우저의 메모리 내에서 채널 이름을 키로 해서 관리된다. - 채널을 동일한 이름으로 생성된 모든 BroadcastChannel 인스턴스에 자동으로 연결된다.
2. 메시지 큐잉 및 전달
- 메시지가 postMessage()를 통해서 전송되면, 브라우저는 해당 메시지를 내부적인 메세지 큐에 넣는다.
- 브라우저는 동일한 채널에 가입된 모든 인스턴스들에게 메시지를 전달한다. 비동기적으로 수행되며, 발신 컨텍스트는 수신 컨텍스트의 응답을 기다리지 않는다.
3. 이벤트 전파
- 메시지가 큐에 삽입되면, 브라우저는 해당 채널에 모든 컨텍스트에 대해 이벤트를 발생시킨다.
- 각 컨텍스트는 자신의 이벤트 루프 내에서 message 이벤트를 수신하며, 등록된 onMessage 이벤트 핸들러가 메시지를 처리한다.
Broadcast Channel API 특징
1. 동기화와 동시성 제어
- 브라우저는 각
BroadcastChannel
인스턴스 간의 동기화를 담당한다. 다른 탭이나 윈도우가 동시에 동일한 채널에 접근하더라도 메시지가 올바르게 전달되도록 보장한다. - 메시지는 순차적으로, 전송된 순서대로 처리되며, 이를 통해서 동시성 문제를 방지한다.
2. 보안 및 격리
BroadcastChannel
은 동일 출처 정책을 준수한다. 채널이 출처가 다른 탭이나 윈도우 간에 격리되어 있음을 의미한다.- 내부적으로 브라우저는 출처 정보를 사용하여 각 채널 범위를 결정하고, 오직 같은 출처의 컨텍스트만 메시지를 수신할 수 있도록 한다.
'시작 > TIL(Today I Learned)' 카테고리의 다른 글
V8 엔진 메모리 구조 - 힙(Heap)과 스택(Stack) (0) | 2024.03.11 |
---|---|
Mockoon을 활용한 프론트엔드 개발 효율성 향상: 가상서버 및 Proxy 설정 (1) | 2024.03.03 |
StyleXjs : StyleX 세팅 및 기초 (feat. NEXT) (1) | 2023.12.26 |
231110 - 카카오 로그인 (feat. KOE010) (0) | 2023.11.10 |
231109 - Monorepo를 이용한 Next.js 프로젝트 구성하기 (feat. pnpm) (1) | 2023.11.09 |
댓글