만족

[React] 애드블록(Adblock) 탐지 hooks 만들기 본문

[React] 애드블록(Adblock) 탐지 hooks 만들기

FrontEnd/React Satisfaction 2022. 1. 29. 23:50

애드블록은 브라우저를 사용할 때 표시되는 광고의 로딩을 막아

빠른 로딩이 가능하고 쾌적한 이용을 할 수 있게 해 주는 확장 프로그램이다.

 

그렇지만 개발자나 운영자 입장에서는 트래픽은 나오는데 수익으로 이어지지 않게 하는 아주 골치아픈 녀석이다.

 

https://adblockplus.org/ko/faq_internal

 

자주 묻는 질문 - 내부 작동 원리

어려운 작업은 실질적으로 게코(Gecko)라는 파이어폭스, 선더버드 그리고 다른 애플러케이션에 내장된 상위 엔진에 의해 이루어집니다. 게코는 "콘텐츠 정책(content policies)"으로 불리는 것을 허용

adblockplus.org

애드블록은 브라우저에서 요청을 보낼 때 그 요청 대상이 등록된 광고 관련 주소일 경우

요청을 드랍시키는 방법으로 광고를 차단한다.

 

애드블록이 차단하는 구글 애드센스

구글 애드센스 역시 애드블록이 차단한다.

 

    <script
      async
      src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"
    ></script>

 

페이지에 애드센스를 등록할 때 html하단에 다음과 같은 코드를 추가한 기억이 있을 것이다.

 

그런데 애드블록이 켜진 상태에서는 아래와 같이 동작한다

기본 초기화 코드를 불러오는 것 자체를 막고 있기 때문에 광고 표시 기능이 무력화된다.

 

여기에서 아이디어를 얻어 애드블럭을 감지해 보자

 

애드블록 차단 감지 아이디어

https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js

로 요청을 보내면 그 요청이 실패한다.

 

따라서 해당 주소로 요청을 보내고 실패하면 애드블록이 감지한다고 볼 수도 있겠다.

(물론 순간적으로 네트워크 상태가 불안정해진다던지 해서 실패할 수도 있다)

 

axios를 사용하면

    axios
      .request({
        url: `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js`
      })
      .then(res => {
        //adblock may be disabled
      })
      .catch(e => {
        //adblock may be enabled
      });

위와 같이 애드블록이 작동중인지를 추정해볼 수 있다.

 

그런데 html과 js모두 adsbygoogle.js를 로드하므로 같은 코드를 두번 로드하고 있다.

 

html에서 해당 스크립트 코드를 제거하고, js에서 로드해서 적용해 보자.

 

    axios
      .request({
        url: `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js`
      })
      .then(res => {
        //adblock may be disabled
        eval(res.data);
      })
      .catch(e => {
        //adblock may be enabled
      });

 

eval을 사용해 해당 url에서 로드한 js코드를 실행시킬 수 있다.

 

해당 작업은 20~30ms정도가 소요된다.

 

이제 js에서 구글 애드센스 js 코드를 로드하고 차단되었는지 여부까지 확인할 수 있게 됐다.

 

애드블록 탐지 hooks

import { useEffect, useState } from "react";
import axios from "axios";

let adblockState = undefined;
const useAdblockState = () => {
  const [isBlocked, setIsBlocked] = useState(adblockState);
  useEffect(() => {
    if (adblockState === undefined) {
      axios
        .request({
          url: `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js`
        })
        .then(res => {
          eval(res.data);
          setIsBlocked(false);
        })
        .catch(e => {
          console.error("maybe adblock is enabled", e);
          setIsBlocked(true);
        });
    }
  }, []);
  useEffect(() => {
    if (typeof isBlocked === "boolean") {
      adblockState = isBlocked;
    }
  }, [isBlocked]);

  return isBlocked;
};

export default useAdblockState;

 

앞서 설명했던 로직을 useEffect에 집어넣고, 결과에 따라 isBlocked 상태를 변화시킨다.

 

hooks 외부에 isBlockedState라는 값을 두었는데, 이것은 애드센스 코드가 중복 로딩되지 않도록 하기 위한 것이다.

 

이미 성공/실패 여부가 한 번이라도 결정되었다면 더 이상 새로 로딩해서 결과를 갱신할 필요는 없다.

 

이제 컴포넌트에서 useAdblockState를 사용해 애드블록 탐지를 할 수 있다.

 

애드블록 탐지 hooks: 중복 로딩 방지와 상태 Sync

만약 App.js에서 ComponentA와 ComponentB가 모두 useAdblockState()를 사용하면 어떻게 될까?

 

동일한 코드를 두 번 로드하여 eval될 것이다.

 

이는 의미없이 네트워크 트래픽을 잡아먹는 좋지 않은 작업이다.

 

import { useEffect, useState } from "react";
import axios from "axios";

//adsense 로딩 여부
let loadingAdsense= false;
let adblockState = undefined;

const useAdblockState = () => {
  const [isBlocked, setIsBlocked] = useState(adblockState);
  useEffect(() => {
    if (loadingAdsense){
      //이미 로딩 작업에 들어갔다면 로딩 시도 안함
      return;
    }
    if (adblockState === undefined) {
      //adsense 코드 로딩 직전 loadingAdsense= true해줌으로써 중복 로딩 막음
      loadingAdsense= true;
      
      axios
        .request({
          url: `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js`
        })
        .then(res => {
          eval(res.data);
          setIsBlocked(false);
        })
        .catch(e => {
          console.error("maybe adblock is enabled", e);
          setIsBlocked(true);
        });
    }
  }, []);
  useEffect(() => {
    if (typeof isBlocked === "boolean") {
      adblockState = isBlocked;
    }
  }, [isBlocked]);

  return isBlocked;
};

export default useAdblockState;

 

loadingAdsense 변수를 활용하여 애드센스 코드 중복 로딩을 막을 수 있다.

 

그런데 여전히 ComponentA와 ComponentB가 거의 동시에 삽입되었을 때,

한쪽에서만 로딩이 걸리고 isBlocked가 변경되기 때문에

다른 한쪽에서는 애드블록 활성화 여부를 수신할 수 없게 된다.

 

따라서 이 때는 전역 상태 관리 도구나 ContextAPI를 이용할 수 있다.

 

그러나 귀찮기 때문에 setInterval로 adBlockState의 값이 boolean타입으로 변경되었는지를 감시하다가,

값이 할당되면 state를 변경하고 타이머를 해제할 것이다.

 

import { useEffect, useState } from "react";
import axios from "axios";

//adsense 로딩 여부
let loadingAdsense= false;
let adblockState = undefined;

const useAdblockState = () => {
  const [isBlocked, setIsBlocked] = useState(adblockState);
  useEffect(() => {
    if (loadingAdsense){
      //이미 로딩 작업에 들어갔다면 로딩 시도 안함
      if (isBlocked === undefined){
        
        //애드블록 상태가 수신되지 않았을 경우 adblockState를 감시하다가
        //state(isBlocked)에 반영
        //0.1s 단위로 감시한다
        const timer= window.setInterval(()=>{
          if (typeof adblockState === 'boolean'){
            setIsBlocked(adblockState);
            //adblockState가 결정되면 타이머 해제
            window.clearInterval(timer);
          }
        }, 100);
        
        //컴포넌트 언마운트시 타이머 해제
        return ()=>{
          window.clearInterval(timer);
        }
      }
      return;
    }
    if (adblockState === undefined) {
      //adsense 코드 로딩 직전 loadingAdsense= true해줌으로써 중복 로딩 막음
      loadingAdsense= true;
      
      axios
        .request({
          url: `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js`
        })
        .then(res => {
          eval(res.data);
          setIsBlocked(false);
        })
        .catch(e => {
          console.error("maybe adblock is enabled", e);
          setIsBlocked(true);
        });
    }
  }, []);
  useEffect(() => {
    if (typeof isBlocked === "boolean") {
      adblockState = isBlocked;
    }
  }, [isBlocked]);

  return isBlocked;
};

export default useAdblockState;

별로 좋은 코드는 아니지만...

 

어쨌든 이렇게 하면 애드센스 중복 로딩을 막고 애드블록 상태 sync를 할 수 있다.



Comments