Context

유저 이벤트 로그를 심다가, Target의 필터 UI가 URL을 바꿀 때 브라우저가 아무 이벤트도 발생시키지 않는다는 걸 알게 됐다. popstate는 뒤로가기/앞으로가기에서만 발생하고, 사이트가 직접 pushState / replaceState로 URL을 갱신할 때는 표준 이벤트가 없다.

그래서 monkey patching으로 직접 훅을 걸었다.

What I Learned

Monkey patching이란 런타임에 이미 정의된 객체/함수의 동작을 외부에서 덮어쓰거나 확장하는 기법이다. 원본 소스 코드는 건드리지 않고, 실행 중인 메모리상의 객체를 바꿔치기한다.

실제 적용 코드

['pushState', 'replaceState'].forEach((m) => {
    const original = history[m];
    history[m] = function (...args) {
        const ret = original.apply(this, args); // 원본 반드시 호출
        window.dispatchEvent(new Event('locationchange'));
        return ret;
    };
});

브라우저 내장 history.pushState를 우리 함수로 교체한다. 이후 누가 호출하든 우리 버전이 실행되고, 커스텀 이벤트를 발생시켜 URL 변화를 감지할 수 있다.

언제 유용한가

  • 브라우저/서드파티 API에 훅을 걸 때 — 수정 불가능한 내장 API를 덮어씌우는 것은 가능
  • 긴급 버그 패치 — upstream 수정 전 임시 우회
  • 계측/모니터링 — Sentry, DataDog 같은 APM 도구들이 fetch, XMLHttpRequest, console.error 등을 몽키 패칭해서 에러/요청을 자동 수집

주의점

위험내용
전역 영향한 번 덮어씌우면 페이지 전체 모든 호출이 영향받음
업스트림 변경원본 API 시그니처가 바뀌면 패치가 조용히 고장날 수 있음
디버깅 난이도스택트레이스에 패치된 함수가 찍혀 원인 추적이 어려워짐
원본 호출 누락original.apply(this, args)를 빼먹으면 원래 동작이 사라짐

Note

가능하면 표준 이벤트/옵저버를 먼저 찾고, 없을 때 최후 수단으로 쓰는 게 좋다. 이번 케이스는 “브라우저가 history 변화에 대한 표준 이벤트를 제공하지 않는다”는 명확한 이유가 있어서 정당화되는 사례다.

← All TIL