# 탭 간의 데이터 공유하는 방법, Broadcast Channel API

웹 어플리케이션을 사용하다보면 같은 사이트를 여러 탭에서 열어두는 경우가 많습니다. 이때 한 탭에서 로그아웃을 했는데 다른 탭은 로그인 화면을 계속 보여주거나, 장바구니가 탭마다 다르게 보이면 사용자 경험은 떨어지게 됩니다.

이러한 문제를 간단하게 해결할 수 있는 브라우저 기능이 `BroadcastChannel` API 입니다. 같은 출처의 탭, 창, 아이프레임, 웹 워커사이에서 메세지를 실시간으로 주고받을 수 있게 해주는 웹 표준 API이며, 별도 라이브러리나 서버 연결없이 사용할 수 있다는 것이 큰 장점입니다. `BroadcastChannel` API는 현재 모던 브라우저에서 지원이 되고 있습니다.

## BroadcastChannel API가 무엇인가요?

BroadcastChannel API는 이름이 같은 채널에 연결된 모든 브라우저 컨텍스트에 메세지를 전송하는 기능입니다.
여기서, 브라우저 컨텍스트란 다음과 같은 실행 공간을 의미합니다.

- 같은 사이트의 다른 탭

- 새 창 또는 팝업 창

- 아이프레임

- 웹 워커

예를 들어 `new BroadcastChannel(“auth”)`로 `auth` 라는 채널을 만들면, 같은 출처에서 `auth` 채널을 구독하고 있는 다른 탭들이 메세지를 받을 수 있습니다. 여기서 중요한 점은 같은 `origin` (프로토콜, 도메인, 포트의 조합)이여야 한다는 점이 관건입니다. **서로 다른 도메인 간의 통신을 위한 도구가 아니기에, 다른 출처의 아이프레임 또는 팝업과 통신을 해야한다면 이런 경우에는 **`**window.postMessage()**`** 를 사용해야합니다.**

## 기본적인 사용 방법

초기 세팅부터 사용 방법이 많이 복잡할 것 같지만, 생성자를 통해 생각보다 쉽고 간단하게 세 단계 정도로 나누어 사용할 수 있습니다.

1. 브로드캐스트로 사용할 채널을 생성합니다.

2. 메세지를 수신할 이벤트 리스너를 등록합니다.

3. `postMessage()` 로 메세지를 보냅니다.

```javascript
const channel = new BroadcastChannel('app-channel');

channel.addEventListener("message", (event) => {
console.log("다른 탭에서 받은 데이터 :", event.data);
});

channel.postMessage({
type: 'HELLO',
message: '안녕하세요'
});
```

이 코드를 같은 사이트의 서로 다른 두 탭에서 실행하게 되면, 한 탭이 보낸 메세지를 다른 탭이 받는 구조가 됩니다. 여기서 확인해야할 점은 메세지를 보낸 `BroadcastChannel` 객체 자신은 메세지를 받지 않는다는 것입니다. 첫 번째 탭에서`channel.postMessage()` 를호출하면 두 번째 탭에서는 메세지를 수신하지만, 첫 번째 탭의 같은 `channel`  객체에는 `message` 이벤트가 발생하지 않습니다.

## 메세지로 전송할 수 있는 데이터 타입

`BroadcastChannel` 은 문자열만 보내는 API가 아닙니다. 객체, 배열, 숫자, 날짜 등 다양한 값을 전송할 수 있습니다.

```javascript
const channel = new BroadcastChannel('user-events');

channel.postMessage({
type: 'USER_UPDATED',
user: {
id: 1,
name: '홍길동',
roles: ["user", "editor"],
},
updatedAt: new Date(),
});
```

데이터는 내부적으로 structured clone algorithm 을 통해 복사되어 전달됩니다. 그래서 보통 `JSON.stringfy()` 와 `JSON.parse()` 를 직접 호출할 필요가 없습니다. 다만 함수, DOM 요소, 일부 특수 객체처럼 복수할 수 없는 값은 전송할 수 없습니다.

## 예제로 확인해보기 : 로그인 상태

가장 대표적인 활용 사례는 보통 로그인과 로그아웃 상태의 동기화 처리입니다. A탭과 B탭이 공존한다고 가정을 해보겠습니다. 사용자가 A탭에서 로그아웃을 하게 되면 B탭도 로그아웃 처리가 되어야합니다. 단순히 토큰을 삭제하는 것만으로는 이미 열려있는 다른 탭의 화면이 자동으로 바뀌지 않기 때문에 탭 간 알림이 필요합니다.

```javascript
const authChannel = new BroadcastChannel('auth');

function logout() {
localStorage.removeItem('accessToken');
authChannel.postMessage({
type: 'LOGOUT'
});

window.location.href = '/login';
}

authChannel.addEventListener('message', (event) => {
if (event.data.type !== 'logout') return;

localStorage.removeItem('accessToken');
window.location.href = '/login');
});
```

A탭에서 로그아웃 이벤트가 발생하게 되면, 토큰 삭제와 더불어 “LOGOUT” 메세지를 전송하게 됩니다. 그러면 나머지 탭에서 메세지를 수신하게 되고 각 탭에서는 메세지를 받아 이벤트(토큰 삭제 및 로그인 페이지로 이동)를 실행하게 됩니다. 만약, 채널 사용이 끝났다면 `BroadcastChannel` 객체에 `close()` 를 호출하여 더 이상 메세지를 받지 않게하면서, 브라우저 상에 해당 채널이 계속 살아있지 않도록 하기 위해 해당 채널 객체를 정리할 수 있도록 해줘야합니다.

## BroadcastChannel이 없던 시절

`BroadcastChannel` 이 없던 시절에는 `localStorage` 의 `storage` 이벤트를 탭 간의 통신 용도로 자주 활용했습니다.

```javascript
window.addEventListener('storage', (event) => {
if (event.key === 'theme') {
document.documentElement.dataset.theme = evet.newValue;
}
});

function changeTheme(theme) {
localStorage.setItem('theme', theme);
document.documentElement.dataset.theme = theme;
});
```

이러한 방법 또한 같은 출처의 다른 탭에서 변경을 감지할 수 있지만, 비교해보면 여러 제약사항이 존재합니다.

- `BroadcastChannel` 방식은 탭 간의 메세지를 전달하지만, `storage` 는 저장소 변경을 감지합니다.

- `BroadcastChannel` 방식은 다양한 복사 가능한 객체를 전송하지만, `storage` 는 문자열만을 전송합니다.

- `BroadcastChannel` 방식은 데이터베이스가 아니기에 데이터를 영구 저장하지 않지만, `storage` 는 브라우저 저장소에 저장됩니다.

- 그 외에 `BroadcastChannel` 방식은 메세지의 의밀르 자유롭게 정의할 수 있는 반면, `storage` 는 저장소 키 변경에 의존성이 있습니다.

## 글을 마무리하며

사실 Agentic UI의 스트리밍 멀티 세션을 구현하던 도중에 어떻게 구현하면 좋을까하다가, 가장 먼저 떠올렸던 웹 API 방식입니다. 한 탭에서 주 요청들을 보내고, 다른 탭에서는 이러한 데이터들을 같이 공유해서 받는 구조인 경우 적합한 방식 중 하나라고 생각했습니다.

`BroadcastChannel API` 는 같은 사이트의 여러 탭을 자연스럽게 동기화하기 위한 간단하고 강력한 웹 표준입니다. 특히 제가 사용하려고 했던 Agentic UI와 더불어 아래와 같은 상황에서 유용하게 사용될 수 있습니다.

- 한 탭의 로그아웃을 모든 탭에 반영해야할 때 유용합니다.

- 테마, 언어, 장바구니 상태를 즉시 동기화할 때 유용합니다.

- 같은 사이트의 탭 사이에 알림이나 이벤트를 전달할 때 유용합니다.

- 웹 워커와 페이지 사이에 간단한 메세지를 전달할 때 유용합니다.

`BroadcastChannel API` 는 데이터를 저장하지 않고, 서로 다른 출처와 통신하지 않습니다. 탭에서 거의 동시에 데이터를 수정하거나 설정을 변경하면 메세지 순서와 저장 상태가 엇갈릴 수 있는데, 간단한 설정 값이라면 마지막 변경을 우선하는 방식으로 충분할 수 있지만 중요한 데이터를 다루는 작업이라면 서버를 최종 기준으로 두고 버전 번호, 갱신 시각, 낙관적 잠금 등의 별도 전략을 택해야합니다. 복잡한 상태 충돌도 해결하지 않기에 이러한 부분들을 유의하여 사용한다면 정말 좋은 서비스의 전략 도구가 될 수 있다고 생각합니다..

For the site tree, see the [root Markdown](https://slashpage.com/timmy.md).
