Sentry에서 발생한 Date에러
Sentry에서 에러가 발생했다. Date객체인 date에서 에러가 발생했다. 이는 Date객체라고 하여 Safari와 Chrome의 Date객체를 해석하는 방식이 다르다는 것 때문에 발생한 것이 아닐까 생각했다.
문제점 : 기존의 useBrowser
export default function useBrowser() {
const [browser, setBrowser] = useState('');
const mobileDevice = getMobileDivce();
useDidMount(() => {
const agent = navigator.userAgent;
if (agent.includes('Chrome')) setBrowser('chrome');
if (agent.includes('Safari') || mobileDevice === 'ios') setBrowser('safari');
});
return browser;
navigator.agent로 분기처리하여 ‘Chrome’을 가지고 있다면 chrome브라우저로, Safari 혹은 ios를 가지고 있다면 safari브라우저로 체크한다. 아이폰 앱의 웹뷰는 Safari 기반, 안드로이드 앱의 웹뷰는 Android 기반으로 동작하기에 채택한 방식이다.
이 useBrowser를 테스트하기 위해 실행을 해보았다.
- Chrome환경 :
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 - safari환경 :
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1.2 Safari/605.1.15
Safari환경에서는 그렇다 쳐도, Chrome환경에서도 AppleWebKit, Safari와 같은 키워드가 들어가 있는 것이다.
navigator.userAgent가 뭔데?
window 전역 객체에는 Location, navigator, web storage등 다양한 프로퍼티가 존재한다. window 전역 객체는 식별자 window.을 앞에 붙이지 않고도 객체 내 프로퍼티에 접근이 가능하다.
그 중 하나가 navigator인데, navigator에는 userAgent라는 객체가 존재한다. userAgent는 Mozilla 버전 + OS 정보 + 렌더링 엔진 정보 + 브라우저 이름의 형태로 이루어진다.
navigator 객체의 경우 location과 달리 writable 속성의 값이 false이므로 직접 값을 할당할 수 없다.
각 브라우저 별 userAgent는 다음과 같다.
- Firefox : Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0
- Chrome : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36
- Safari : Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1
- Edge : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36
- IE : IE11 : Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko
- 여기서 Mozilla는 인터넷 소프트웨어와 웹 기술에 주력하는 비영리 조직이다. 이들은 Firefox를 개발했으며, 웹 표준과 개방형 인터넷을 촉진하는 데 중요한 역할을 한다.
Chrome과 Safari 모두 AppleWebKit을 렌더링 엔진으로 사용하는 것을 확인할 수 있다. Webkit은 원래 애플이 Safari 브라우저를 위해 개발한 렌더링 엔진이다. Google Chrome도 초기에는 Webkit엔진을 사용했었으며, 초기 Chrome브라우저는 Webkit기반으로 개발되었고, 이로 인해 과거의 호환성을 위해 ‘AppleWebKit’이 userAgent에 포함되었다. 이전에 작성한 코드와 규칙이 계속 작동하도록 보장하기 위함이다. 또한, 마지막에 Safari가 붙은 이유도 동일한 이유이다.
다른 프로젝트에서는 브라우저를 어떻게 구분할까?
-
toss/slash
// isIOS export function isIOS() { if (isServer()) { return false; } return navigator.userAgent.match(/ipad|iphone/i) != null; } // isAndroid export function isAndroid() { if (isServer()) { return false; } return navigator.userAgent.match(/Android/i) != null; }
Safari와 Chrome에 대한 구분은 아니고, iOS와 Android에 대한 구분이다.
결론
크롬과 사파리 모두 navigator.userAgent값으로 Safari를 가지고 있어 발생한 에러였다. 이는 크롬이 초기에 Safari의 WebKit을 사용했었기에 때문에 기존에 작성되었던 것이며, 호환을 위해 아직까지 제거하지 않고 있는 것이다. 현재 크롬은 Webkit이 아닌 Blink 렌더링 엔진을 사용한다.
window전역객체가 가진 navigator.userAgent로 구분하는 것이 브라우저를 구분하는 최선의 방법이다. Chrome은 크롬 브라우저의 userAgent만 가지고 있기에 먼저 분기처리를 해주고, Safari를 가지고 있다면 사파리 브라우저로 분기처리를 해주었다.
export default function useBrowser() {
const [browser, setBrowser] = useState('');
useDidMount(() => {
if (navigator.userAgent.match(/Chrome/i) != null) setBrowser('chrome');
else if (navigator.userAgent.match(/Safari/i)) setBrowser('safari');
});
return browser;
}
그리고, userAgent는 브라우저 전역객체의 navigator가 가지고 있는 객체로, 서버에서는 접근하지 못한다. 그래서 useDidMount훅을 이용하여 브라우저에서만 실행되도록 하였다.