[RN] 코드 인젝션으로 웹페이지 동작 수정하기
[RN] 코드 인젝션으로 웹페이지 동작 수정하기
RN
, AI Tools
, react-native-webview
글의 순서
- 문제 상황
- 요구사항 파악하기
- 괜찮은 해결 방법 찾기
- 몰랐던 사실 & 느낀 점
문제상황
어느 날 앱 개발자로부터 도착한 메세지
👨💻(앱 개발자): Next routing 사용해서 화면 전환 하면 client-side 화면 이동만 되나요?
👨💻(앱 개발자): Exhibition 페이지에서 router.push로 화면이동할때 캐치해야하는데 캐치가 안되어서요.
- 사전 설명
- 앱에서 일부 페이지를 웹뷰로 사용하고 있습니다.
- 그 동안은 Privacy Policy와 같이 사용자 상호작용이 없는 페이지만 웹뷰로 사용했고, Header나 Footer 등은 가려서 사용자가 웹뷰 안에서 페이지 이동할 우려가 없었습니다.
- 그런데 이번에 작품들을 전시 형식으로 보여주는 Exhibition 페이지를 웹뷰로 사용하게 되면서 사용자가 웹뷰내에서 페이지이동을 시도하면 그것을 막아야할 필요가 생겼습니다.
- 앱에서 일부 페이지를 웹뷰로 사용하고 있습니다.
- 따라서 앱 개발자가 페이지 이동을 감지하여 막으려고 한 것이죠. 그런데 생각대로 되지 않았습니다. 이유는, Next.js의 라우터는 클라이언트 사이드 렌더링을 하기 때문이죠.
- 결론: 웹뷰내에서 페이지 이동을 막을 방법이 필요합니다.
- 앱 개발자의 요구사항
- 전시(Exhibition) 페이지에서 라우팅시 페이지를 Reload 해줄 수 없나요?
요구사항 파악하기
문제가 되는 상황을 모두 파악하기
- Exhibition에서 문제가 되는 상황, 즉 화면 전환을 캐치 해야 하는 상황은 아래와 같습니다.
- 페이지 이동이 일어나는 경우1: 작품 클릭
- 작품을 누르면 작품 상세 모달이 뜨고, 거기서 버튼을 누르면 화면이 이동됩니다.
- Buy 버튼: 구매 페이지로 이동 -> 웹 페이지 이동을 막고, 앱의 구매 페이지로 이동해야함
- Detail 버튼: 작품 상세 페이지로 이동 -> 웹 페이지 이동을 막고, 앱의 작품 상세 페이지로 이동해야함
- 페이지 이동이 일어나는 경우2: Exit 버튼 클릭
- Exit 버튼을 누르면 뒤로가기 처리된다. -> 웹 페이지 뒤로가기를 막고, 앱내에서의 이전 위치로 이동해야 합니다.
문제와 관련하여 요구사항 분석하기
저에게 처음 주어진 요구사항은 "앱에서 페이지 변경을 감지할 수 있도록 Reload 하기"로 보였습니다.
그런데 페이지 이동을 캐치하는 이유를 다시 생각해보면
- 웹뷰의 페이지 이동을 다른 행동으로 대체하기 위해(ex: 앱의 상품 디테일 화면으로 이동)
앱에서 Routing을 핸들링 할 수 있으면 문제가 해결 된다는 사실을 알 수 있습니다.
괜찮은 해결 방법 찾기
해결책 1: JavaScript Injection
- 익숙한 문제가 아니기 때문에 Claude AI에게 일반적인 해결책을 물어보았습니다.
- Claude AI가 알려준 방법은 JavaScript Injection을 사용하는 것이었습니다.
- 알려준 방법을 그대로 사용할 수는 없었지만 버튼 클릭 동작을 인터셉트 할 수 있으면 좋겠다는 생각이 들어 앱 개발자에게 공유하였습니다.
해결책 2: Query Params
- 사실 가장 먼저 머리속에 떠오른 방법은 header와 footer를 가리는 데 사용했던 Query Params로 fullReload를 컨트롤 하는 방법입니다.
- Query Params에 fullReload가 true인 경우에만 router.push대신 window.location.href를 사용하여 전체 리로드 일어나게 분기하는 방법입니다.
const fullReload = queryParams.fullReload === 'true';
if (fullReload) {
// fullReload가 true인 경우 전체 페이지 리로드
window.location.href = path;
} else {
// 그 외의 경우 router.push 사용
router.push(path);
};
- 동작은 하지만 좋은 방법이라고 생각되지 않아서 사용하지 않았습니다.
- 좋은 방법이 아닌 이유1: front에서 처리해야하는 쿼리 파라미터가 추가되어 복잡도가 높아짐
- 좋은 방법이 아닌 이유2: 웹 개발자가 실수로 분기를 삭제하거나 쿼리 파라미터를 무시하면 쉽게 깨짐
- 좋은 방법이 아닌 이유3: 웹뷰 진입시 해당 쿼리를 달고 들어오면 무조건 페이지 변경 시 Full Reload가 일어나게 됨(경우에 따라 Full Reload를 선택 적용하는 것이 불가)
- 좋은 방법이 아닌 이유4: 프론트에서 코드만 보고는 무슨 의도로 작성된 코드인지 한번에 알아채기 힘듦.(명확하지 않음)
해결책2(JavaScript Injection)의 구현
- 끌로드가 일반적인 해결책이라고 알려준 JavaScript Injection으로 구현해보기로 합니다.
- 다시 한번 경우의 수를 생각해보면,
- 작품 클릭 > 버튼 클릭 > 페이지 이동
- exit 버튼 클릭 > 페이지 이동
- 모든 경우의 수가 Router(페이지 이동)와 관련되어 있다.
- 그러면 Code Injection을 통해서, Router의 동작을 변경해보는 것은 어떨까요?
- 가능여부를 Claude에게 물어보고 아래와 같은 결과물을 도출할 수 있었습니다.
// router의 동작을 컨트롤 하기 위한 code injection.
// 아래의 함수를 WebView 컨포넌트의
// injectedJavaScript,injectedJavaScriptBeforeContentLoaded
// props에 넘겨주어야 합니다.
const injectedJavaScript = `
(function() {
const overrideRouterPush = function() {
window.isNativeApp = true;
const originalPush = window.next.router.push;
window.next.router.push = function(url, as, options) {
const shouldInterceptPush = [
'/buy',
'/artwork'
].some(pattern => url?.includes(pattern));
if (shouldInterceptPush) {
window.ReactNativeWebView.postMessage(JSON.stringify({
event: 'routerPush',
data: {
url: url,
},
}));
return;
}
originalPush.call(this, url, as, options);
};
};
window.onload = overrideRouterPush;
})();
`;
- 그러면 Query Params를 사용하는 방법에서는 불가능했던 선택적용(경우에 따라 기존의 router.push 사용 or 페이지 이동 막음)을 앱에서 컨트롤 가능합니다.
- Step1: Javascript Injection으로 Next.js의 router.push를 modify(메세지 전송, 위의 코드 참조)
- Step2: onMessage에서 메시지를 받아서 분기처리(아래 코드 참조)
// WebView 컴포넌트에 넘기는 onMessage props의 예시
// 여기서 javascript codeinjection에서 넘긴 메세지를 받아 처리할 수 있다.
const onMessage = (event) => {
const message = JSON.parse(event.nativeEvent.data) as Message;
if (message.event === 'routerPush') {
const url = getUrl(message.data.url);
if (url === 'artwork') {
// 앱의 아트워크 디테일 페이지로 이동
return false;
};
if (url === 'buy') {
// 앱의 구매 페이지로 이동
return false;
};
};
return true;
};
해결책 1과 해결책 2의 차이
- Code Injection을 사용하는 방법에서는 Query Params를 이용하는 방법(해결책2)과 비교해보면 앱에서 필요한 요구사항을 앱에서 작성하므로 혼란의 여지가 적습니다.
- 프론트 개발자와 소통하지 않고 직접 컨트롤이 가능하며 프론트에서 실수하여 버그가 생길 우려가 비교적 적습니다.
몰랐던 사실 & 느낀 점
- 안드로이드는 injectedJavaScriptBeforeContentLoaded를 붙여야 합니다.
- Claude AI 에게 RN 관련 코드를 물어볼때는 '안드로이드에서 작동하게 하려면 추가할 것은 없습니까?'라고 한번 더 물어보는 것이 좋을 것 같습니다.
- 입사 후 1-2개월간 앱과 웹을 모두 작업했는데, 그 이후로는 웹쪽만 작업하여 오랜만에 앱 작업을 했습니다. 처음에는 테스트 용으로 작성했는데 그 코드가 실제로 적용되어 뿌듯하기도 했고요.
- IOS와 안드로이드를 각각 시뮬레이터와 에뮬레이터로 세팅하는 것도 오랜만에 하다보니 시간이 꽤 걸렸습니다.
- 요구사항이 있다고 해서 그대로 작업하는 것이 아니라 분석을 해보는 자세가 좋은 것 같고, 시간은 더 걸렸지만 더 나은 방법으로 작업한 것 같습니다.