데모 영상이 곧 공개됩니다...
Twitch를 시청하는 동안 보너스 상자를 자동으로 받는 Chrome 확장 프로그램 · 의미 기반 선택자 체인으로 난독화된 CSS가 바뀌어도 안정적으로 동작 · 백엔드 없음
아키텍처
- Chrome Web Store, 배포 · 스토어, 외부 서비스, v1.4.5 · auto-updates
- Stats 팝업, 프런트엔드 · 팝업, popup.html · popup.js
- 자동 수령 루프, 핵심 · 콘텐츠 스크립트, content.js · injected on twitch.tv/*, 5초 인터벌, 선택자 체인, closest('button') 올라가기, readyState 가드, 비활성화 확인
- Twitch 라이브 페이지, 호스트 페이지 · DOM, 외부 서비스, 커뮤니티 포인트 요약
- chrome.storage.local, 저장소 · 로컬, claimedBonusChestCount
- Chrome Web Store에서 자동 수령 루프 방향으로: 업데이트 자동 전달
- 자동 수령 루프에서 Twitch 라이브 페이지 방향으로: 확인 + 클릭 · 5초마다
- 자동 수령 루프에서 chrome.storage.local 방향으로: 카운트 +1 · 수령 시
- Stats 팝업에서 chrome.storage.local 방향으로: 통계 읽기 · 열릴 때
Chrome은 모든 twitch.tv 페이지에 content.js를 삽입합니다. readyState 검사로 load 이벤트 발생 여부와 관계없이 루프를 시작하고, 이후 5초마다 확인합니다. findClaimButton()은 먼저 커뮤니티 포인트 영역으로 검색 범위를 좁힌 뒤 의미 기반 선택자 4개를 우선순위대로 확인합니다. 상자가 나타나면 closest()로 상위 <button>을 찾고 활성 상태인지 확인한 뒤 클릭합니다. 수령에 성공하면 chrome.storage.local의 claimedBonusChestCount를 늘립니다. 통계 팝업은 이 값을 읽어 지금까지 받은 채널 포인트를 계산합니다.
배포: Extension → Chrome Web Store · MV3, 모든 것이 클라이언트 측에서 실행됩니다
성과
- ≤ 5초 · 상자 표시됨 → 수령됨
- 0 · 서버 · 모든 로직이 탭 안에서 실행됨
- 4개 · 화면 모양보다 의미를 우선한 대체 선택자
- 2개 · 전체 권한: storage + twitch.tv 호스트
보여준 역량: 견고한 DOM 선택자 전략 · Chrome 확장 프로그램 MV3 · SPA를 고려한 콘텐츠 스크립트 · 최소 권한 설계
문제와 해결책
핵심 제약은 확장 프로그램이 직접 제어할 수 없고 예고 없이 바뀌는 페이지 안에서 동작한다는 점입니다.
▓ Twitch의 CSS 변경에 대응하기
문제: Twitch는 styled-components로 UI를 구성합니다. ScCoreButton-sc-ocjdkq-0 같은 클래스 이름은 빌드 과정에서 생성되며 프론트엔드를 다시 빌드할 때마다 바뀝니다. 초기 버전은 이런 클래스에 의존해 아무런 경고 없이 작동을 멈췄고, Git 이력에서도 선택자 수정만으로 릴리스한 기록이 네 번이나 있습니다.
해시 기반 선택자를 의미가 명확한 선택자 4개의 우선순위 체인으로 교체했습니다. 상자 아이콘 클래스, Twitch의 data-test-selector 속성 두 개, data-a-target 속성 하나를 사용합니다. 내부 아이콘이 선택될 수도 있으므로 closest('button')으로 클릭 가능한 상위 요소를 찾습니다.
- 해시 클래스는 안정적인 API가 아닌 빌드 결과이므로 사용하지 않았습니다
- 버튼 텍스트는 언어가 바뀌면 작동하지 않으므로 제외했습니다
- 테스트용 속성도 Twitch 내부 구현이므로 하나에 의존하지 않고 대체 선택자 4개를 뒀습니다
▒ MutationObserver 대신 폴링
문제: 상자는 예측할 수 없는 순간에 나타나고, 감지는 이미 비디오 재생으로 바쁜 탭 안에서 실행됩니다. 여기서 잘못된 선택은 모든 채팅 메시지마다 CPU를 소모합니다.
검색 범위를 좁힌 querySelector 하나를 5초 간격의 setInterval로 실행합니다. Twitch 상자가 약 15분마다 나타난다고 보면 상자 하나당 약 180번의 가벼운 검사를 수행합니다.
- 먼저 MutationObserver를 시도했지만 Twitch 채팅이 계속 바뀌어 콜백이 지나치게 많이 실행됐습니다. 실험 코드는
content copy.js에 남아 있습니다 - 단순하고 안정적인 루프를 쓰는 대신 수령이 최대 5초 늦어질 수 있습니다
- 채널을 바꿔도 문서가 유지되므로 같은 타이머가 Twitch의 SPA 이동 후에도 계속 동작합니다
▚ 백그라운드 서비스 워커 없음
문제: MV3 서비스 워커에 로직을 넣으면 구성 요소와 생명주기 관련 오류가 늘고 Web Store 심사 범위도 넓어집니다.
확장 프로그램 전체는 콘텐츠 스크립트 하나와 팝업 하나입니다. twitch.tv 호스트 외에 유일한 권한은 storage뿐입니다.
- Twitch 탭이 닫혀 있으면 할 일이 없으므로 alarms API를 쓰는 백그라운드 워커는 제외했습니다
- 권한이 두 개뿐이라 스토어 심사가 간단하고 사용자가 설치 내용을 쉽게 확인할 수 있습니다
- 탭끼리 조율하지 않아 각 탭이 따로 확인하지만, 이미 받은 상자는 비활성 상태라 다시 클릭되지 않습니다
▧ SPA에서 콘텐츠 스크립트 삽입 시점
문제: 콘텐츠 스크립트는 페이지의 load 이벤트 전이나 후에 실행될 수 있고, Twitch는 pushState로 채널 사이를 이동하며 절대 새로고침하지 않습니다. 초기 버전은 사용자가 채널을 전환한 뒤 모든 상자를 놓쳤습니다.
readyState 검사는 load가 이미 발생했으면 루프를 즉시 시작하고, 아직이라면 이벤트를 기다립니다. 이후 같은 타이머가 모든 앱 내부 이동에서도 유지됩니다.
- 기존 타이머만으로 해결되므로 추가 권한이 필요한 webNavigation 기반 재삽입은 제외했습니다
- 늦게 삽입된 스크립트가 이미 지나간
load이벤트를 계속 기다리는 경쟁 조건을 막습니다 - 타이머 하나가 같은 탭에서 방문하는 모든 채널을 처리합니다
▤ 백엔드 없는 통계
문제: 사용자는 확장 프로그램이 실제로 동작하는지 확인하고 싶지만, 원격 분석 서버를 운영하면 무료 도구에 비용과 유지보수, 개인정보 보호 책임이 생깁니다.
chrome.storage.local에 카운터 하나를 두고 상자를 받을 때마다 늘립니다. 팝업은 이 값을 읽어 획득한 채널 포인트를 계산합니다(x50).
- 원격 분석을 전혀 사용하지 않아 데이터가 브라우저 밖으로 나가지 않습니다
- 카운터는 기기에만 저장돼 여러 기기에서 동기화되지 않지만 단순 참고용 통계에는 충분합니다
- 비동기 스토리지 API로 값을 읽고 수정해 팝업과 콘텐츠 스크립트가 서로 직접 의존하지 않습니다