[Chrome Extension] 확장앱 WebSocket 사용자 정의 Header 지정

동재·2025년 3월 26일

아래 글은 Chrome Extension(확장앱)에서 WebSocket 요청에 사용자 정의 헤더를 지정하기 위한 고군분투 과정을 정리한 글입니다.

1. 들어가며

프로젝트 진행 중, Client(확장앱)에서 WebSocket Connection 요청을 보낼 때 USER_ID: 1 같은 사용자 정의 헤더를 지정해야 했습니다.

브라우저에서 기본으로 지원하는 WebSocket (new WebSocket(url)) 으로는 사용자 정의 헤더 지정이 불가능하기 때문에, 나름 다양한 방법을 찾아보았지만 방법이 없어 꽤나 막막했습니다.

소켓 통신에서 커스텀 헤더를 넣으려면 SockJS 등의 라이브러리를 Client와 Server 양측에서 모두 사용해야 하는데, 이미 서버는 구현이 완료되어 실서버에서 돌아가는 상황이었기에 새로운 라이브러리를 적용하기가 어려웠습니다.

때문에 “안되는 건가...🥹” 하고 포기하려 하다, 문득 아이디어가 떠올랐습니다.

“Chrome declarativeNetRequest API로 WS 요청을 감지해 헤더를 수정할 수 있지 않을까?”

2. 기본 WebSocket에서 헤더 지정이 불가능한 이유

일반적으로 자바스크립트에서는 WebSocket을 다음과 같이 생성합니다.

const ws = new WebSocket('ws://test.com');

이렇게 생성한 WebSocket 객체에는 직접 헤더를 설정할 인터페이스가 없습니다.

SockJS 라이브러리를 쓰면 해결할 수 있지만, 서버도 동일 라이브러리를 적용해야 하므로 일방적으로 클라이언트에서만 사용할 수 없었습니다.

3. Foreground(확장앱 페이지)에서의 시도

declarativeNetRequest를 통해 특정 리소스 유형(웹소켓, XHR 등)에 대한 요청을 감지하고, 그 요청의 헤더를 수정할 수 있습니다.

아래와 같이 manifest.json 권한에 declarativeNetRequest를 추가해줍니다.

{
  "permissions": [
    "declarativeNetRequest"
   ],
  // ...
}

이후, 확장앱 페이지(예: Page.js)에서 다음과 같이 코드를 작성합니다.

const ruleId = 1; // 고유한 규칙 ID
const studentId = 1; // 예시로 USER_ID에 들어갈 값

chrome.declarativeNetRequest.updateDynamicRules({
  removeRuleIds: [ruleId], 
  addRules: [
    {
      id: ruleId,
      priority: 1,
      action: {
        type: "modifyHeaders",
        requestHeaders: [
          {
            header: "USER_ID",
            operation: "set",
            value: String(studentId),
          },
        ],
      },
      condition: {
        urlFilter: "ws://*", // WebSocket 요청만 감지
        resourceTypes: ["websocket"],
      },
    },
  ],
}, () => {
  if (chrome.runtime.lastError) {
    console.error("Error updating rules:", chrome.runtime.lastError);
  } else {
    console.log(`Rule updated with USER_ID: ${studentId}`);
  }
});

이렇게 설정하고 ws 연결 시 호출하면, 해당 확장앱 페이지가 동작 중인 탭에서 발생하는 ws:// 요청을 가로채 USER_ID 헤더를 강제로 설정해줍니다.

이 방식을 택한 이유는 StudentId를 동적으로 설정하기 위함입니다. DynamicRules를 미리 지정해둘 수도 있지만, 그 경우 실행 시점에 생성되는 StudentId를 반영할 수 없습니다. 따라서 규칙을 매번 업데이트하는 방식을 통해, 실제 요청 시점에 동적 StudentId를 헤더에 포함시키도록 한 것입니다.

동작 확인
웹소켓 요청을 발생시키면, 개발자도구 Network 탭 또는 서버 로그를 통해 USER_ID 헤더가 정상적으로 포함되어 전송되는 걸 확인할 수 있습니다.

4. 한계: Background(Service Worker) 환경에서 문제 발생

Foreground에서는 성공했지만, Background(Service Worker) 환경에서 declarativeNetRequest로 ws:// 요청을 잡아 헤더를 추가하려고 하자, 의도대로 동작하지 않았습니다.

4.1 왜 Background에서 웹소켓을 유지하려고 했나?

Foreground(확장앱 페이지)는 사용자가 페이지를 닫는 순간 웹소켓 연결이 종료됩니다.
반면, Background(Service Worker)는 브라우저가 완전히 종료되지 않는 한 계속 유지될 수 있기에, 연결을 장시간 유지하기 적합합니다.

이번 프로젝트에서는 “확장앱 페이지가 닫힌 뒤에도 서버와의 WebSocket 연결이 계속 이어져야 한다”는 요구사항이 있었기에, Background(Service Worker) 환경에서 웹소켓을 구동하고자 했습니다.

4.2 실패 원인💢

처음에는 크롬 확장앱 자체의 구조적 제한 때문이라고 의심했으나, 찾아보니 Service-Worker에서 declarativeNetRequest 사용 관련 버그가 보고된 상황이었습니다.

관련 버그:
https://issues.chromium.org/issues/40815149?utm_source=chatgpt.com

해당 문제를 어떻게든 우회해보고자, content-scripts를 사용하는 방법을 시도했습니다.

5. Content Script로 헤더 수정 시도

Content Script는 chrome.scripting.executeScript 를 통해 일반 웹사이트에 코드를 주입하여 동작시킬 수 있습니다.

5.1 기대했던 점

탭 내부, 즉 Foreground에서 실행되므로 declarativeNetRequest가 정상적으로 동작하여 웹소켓 요청에 헤더가 수정될 것으로 기대했습니다.

5.2 하지만...

직접 테스트해본 결과, Content Script를 통한 해결은 기대에 미치지 못했습니다.

Foreground(확장앱 페이지)에서처럼 명확하게 웹소켓 요청이 수정되는 것을 확인하기 어려웠으며, 결국 Service Worker에서 WS 요청 시 declarativeNetRequest를 사용해 Header를 추가하는 작업은 실패했습니다. 🥹

6. 마무리

정리하자면,

  • Chrome 확장앱에서는 ws:// 요청에 커스텀 헤더를 붙이는 것이 declarativeNetRequest를 통해 어느 정도 가능합니다.
  • 하지만 Background(Service Worker) 환경에서는, 현재까지 존재하는 버그로 인해 해당 기능을 구현하는 것이 불가능합니다.
  • 이번 포스팅이 Foreground(확장앱 페이지) 쪽에서 구현을 고려하시는 분들께 도움이 되었으면 합니다.

“버그 때문에 실패라니 ㅡㅡ💢”

아쉬운 결과이지만, 언젠가 관련 버그가 해결될지도 모르니 계속 모니터링할 예정입니다.
비슷한 문제로 고민 중이신 분들께 이번 글이 조금이나마 도움이 되길 바랍니다.

profile
Backend Developer

0개의 댓글