서비스 워커를 활용한 핫와이어 오프라인 모드 구현 여정

Rosa Gutiérrez - Offline Mode to Hotwire with Service Workers

작성자
Ruby on Rails Youtube
발행일
2025년 09월 16일

핵심 요약

  • 1 핫와이어 네이티브 앱의 범용 오프라인 모드 지원을 위해 서비스 워커를 활용하는 프로젝트가 진행 중이며, 이는 오랜 요청 사항을 해결하는 것을 목표로 합니다.
  • 2 서비스 워커의 복잡한 생명주기, 웹 뷰 사용자 에이전트 처리, 스코프 리셋 문제, 브라우저별 불일치 등 다양한 기술적 난관을 겪으며 해결 방안을 모색했습니다.
  • 3 Turbo에 기본 오프라인 지원(Phase 1)을 위한 PR이 제출되었으며, 향후 오프라인 매니페스트, 아웃오브밴드 캐싱, Action Cable 연동을 통한 고급 기능(Phase 2) 구현이 계획되어 있습니다.

도입

본 발표는 37 Signals의 이메일 서비스인 Hey 앱에 오프라인 모드를 구현하기 위해 서비스 워커를 활용한 경험을 공유합니다. 기존 Android 및 iOS 네이티브 앱의 오프라인 구현은 각기 다르고 유지보수에 어려움이 많아, 두 앱 간 공유 가능한 범용 솔루션의 필요성이 제기되었습니다. 또한, 핫와이어 네이티브를 사용하는 많은 개발자들이 오프라인 모드 지원을 요청해왔습니다. 서비스 워커는 PWA(Progressive Web App)의 오프라인 접근을 위한 검증된 솔루션으로, 핫와이어 생태계에 이러한 범용 솔루션을 제공하기 위한 완벽한 후보로 선정되었습니다. 하지만 iOS 웹 뷰에서 서비스 워커 지원이 늦어져 2019년 Hey 앱 개발 당시에는 활용할 수 없었습니다.

서비스 워커의 이해와 초기 난관

서비스 워커는 브라우저의 백그라운드 스레드에서 실행되며, 네트워크 프록시처럼 요청을 가로채고 리소스를 캐싱하여 오프라인 접근, 백그라운드 동기화, 푸시 알림 등의 기능을 가능하게 합니다. 서비스 워커의 생명주기(등록, 설치, 활성화, 제어)는 오프라인 우선(offline-first) 전략을 가능하게 하지만, 동시에 가장 복잡한 부분으로 꼽힙니다. clients.claim이나 self.skipWaiting과 같은 API는 신중하게 사용해야 하며, 개발 시 Chrome의 ‘update on reload’ 기능은 유용합니다.

초기에는 네트워크 오류 시 커스텀 오프라인 페이지를 보여주는 간단한 서비스 워커를 배포했습니다. 하지만 이 간단한 구현으로 인해 Android 앱에서 이중 툴바(double toolbars)가 나타나는 문제가 발생했습니다. 이는 서비스 워커가 웹 뷰에서 오버라이드된 사용자 에이전트(User-Agent)를 무시하고 기본값을 사용하면서, 핫와이어 네이티브 앱에서 온 요청임을 서버가 인지하지 못해 웹 앱의 UI가 잘못 로드되었기 때문입니다.

사용자 에이전트 문제 해결

이 문제를 해결하기 위해 메인 JavaScript에서 오버라이드된 사용자 에이전트를 서비스 워커로 메시지 전송하고, 서비스 워커는 이를 커스텀 헤더(X-User-Agent)로 요청에 포함하여 서버로 전달하는 방식을 채택했습니다. 이는 Chromium의 9년 된 버그로 인해 User-Agent 헤더를 직접 설정할 수 없었기 때문이며, 서버 측 변경이 필요했습니다. 이 방법으로 문제는 해결되었으나, 이후 또 다른 난관에 봉착했습니다.

서비스 워커 스코프 리셋과 캐시 무효화

Hey 앱의 오프라인 모드는 사용자가 접근하지 않은 이메일도 사전 캐싱하여 오프라인에서 제공하는 것을 목표로 했습니다. 이를 위해 사용자별 오프라인 매니페스트를 구현하여 서비스 워커가 미리 URL을 가져오고 관련 리소스를 파싱하여 캐싱하도록 했습니다. Chrome에서 성공적으로 작동하던 이 기능이 Firefox에서는 전혀 작동하지 않는 문제가 발생했습니다. 디버깅 결과, 서비스 워커가 정지되었다가 다시 활성화될 때 전역 변수가 초기화되는 ‘스코프 리셋(scope reset)’ 현상 때문이었습니다. 개발 중 Chrome에서 문제가 없었던 이유는 개발자 도구가 열려 있을 때 서비스 워커가 정지되지 않기 때문이었습니다. 이 문제를 해결하기 위해 사용자 에이전트 정보를 전역 변수 대신 메인 스레드에서 설정한 쿠키로 전달하고, 서버에서 이 쿠키를 읽는 방식을 사용했습니다.

캐시 무효화는 오프라인 모드에서 핵심적인 과제입니다. 이메일 업데이트(새로운 답장)나 삭제 시 캐시된 내용을 갱신해야 합니다. 이를 위해 오프라인 매니페스트에 리소스의 최종 업데이트 정보를 추가하고, 서비스 워커는 IndexDB를 사용하여 각 URL의 마지막 캐싱 시간을 기록하는 캐시 레지스트리를 구현했습니다. 또한, 메인 앱에서 특정 이벤트(예: 오프라인 설정 변경) 발생 시 서비스 워커에게 매니페스트 재로드를 지시하는 메시지를 트리거했습니다. 더 나아가, Action Cable을 활용하여 백엔드에서 새로운 이메일 수신과 같은 이벤트 발생 시 오프라인 업데이트 채널을 통해 서비스 워커에게 실시간으로 캐시 무효화 메시지를 전달하는 고급 전략을 구현했습니다.

Hotwire/Turbo 통합 계획

이러한 개념 증명(Proof of Concept) 작업을 바탕으로, Hotwire 및 Turbo에 범용 오프라인 모드를 통합하는 작업을 진행 중입니다. 1단계(Phase 1)에서는 turbo-offline 스크립트를 통해 네트워크 우선(network first), 캐시 우선(cache first) 등 다양한 캐싱 전략과 최대 캐시 유지 기간을 설정할 수 있는 기본적인 오프라인 지원 PR을 Turbo에 제출했습니다. 2단계(Phase 2)에서는 오프라인 매니페스트, 아웃오브밴드 캐싱, Action Cable을 통한 실시간 캐시 무효화 등 앞서 설명한 고급 기능을 통합하고, JavaScript 작성 없이 Rails 라이브러리에서 쉽게 설정할 수 있는 헬퍼를 제공할 계획입니다.

결론

서비스 워커를 활용한 핫와이어 오프라인 모드 구현 프로젝트는 복잡한 서비스 워커의 생명주기, 브라우저 간의 미묘한 차이, 사용자 에이전트 처리, 그리고 캐시 무효화와 같은 다양한 기술적 난관을 극복하는 과정이었습니다. 현재 Turbo PR 통합 및 Hey 앱의 네이티브 구현을 서비스 워커 기반으로 대체하는 작업이 진행 중이며, 궁극적으로 핫와이어 생태계에 강력한 오프라인 기능을 제공하는 것을 목표로 합니다. 이 과정은 '자신감 기반 개발(confidence-driven development)'이라는 유머러스한 표현처럼, 예상치 못한 문제들로 가득했지만, 이를 통해 서비스 워커에 대한 깊은 이해를 얻을 수 있었습니다.

댓글 0

댓글 작성

댓글 삭제 시 비밀번호가 필요합니다.

이미 계정이 있으신가요? 로그인 후 댓글을 작성하세요.

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!