NextJS & ios wkWebview 로컬스토리지 초기화되는 문제 해결

SangminL96·2023년 5월 4일
0

안녕하세요

웹을 개발하면서 최근 검색어나 "오늘 하루 안보기" 팝업 플래그등
브라우저 로컬스토리지를 사용을 자주 하는데요 저는 갤럭시를 사용하기 때문에 인지하지 못했지만
아이폰IOS 아이웨딩앱에서 로컬스토리지가 계속 초기화가 되는 문제가 있었습니다.

문제1

IOS wkwebview를 사용하면 모든 저장매체를 초기화하여 호출하는 걸로 알고 있고
현재 아이웨딩 개발자중 IOS개발자는 없고 전 ios개발자였던 퇴사자에게 외주를 맡겨 이슈가 큰 문제들만 외주를
맡기는 상황입니다

문제2

IOS를 제외한 user-agent는 웹,안드로이드,크롬,사파리 전부 가능한 문제

문제 해결을 위한 방안

  1. ios에서 wkwebview를 사용할 때마다 javascript코드도 같이 넘겨 로컬스토리지 셋팅 (ios개발자가 없고 앱 업데이트가 불가한 상황이라 패스)
  2. ios에서 로컬스토리지 저장을 분기처리하여 redis에 조회/저장 (비로그인시 유저의 고유 아이디를 알 수가 없어 비로그인 상태에서 불가)
  3. ios에서 로컬스토리지 저장을 분기처리하여 redis 조회/저장 다만 유저의 고유 아이디는 Device_uuid로 key값으로 하여 조회/저장
    현재 ios에 device_uuid는 header로 보내주는 로직이 이미 존재하기 때문에 redis와 Device_uuid를 활용하여 문제를 해결하기로함

해결과정 1.

일단 IOS일 경우 localstorage setItem할때 레디스 서버에 key/value값을 저장해야 했다

클라이언트

import { isIOS } from 'react-device-detect';

export const setStore = (key: string, value: any) => {
  if (isIOS) {
    cacheSetLocalstorage(key, value);
  } 
  return localStorage.setItem(key, JSON.stringify(value));
  
};

백엔드

key값은 디바이스 UUID로 고유번호로 구분
기존에 value가 있으면 가져오고 새로운 값 저장

const get = await this.cacheService.get(`${device_uuid}_localstorage`);
    await this.cacheService.set(
      `${device_uuid}_localstorage`,
      { ...get, [key]: value }
    );

해결과정 2.

처음에는 로컬스토리지 get을 사용할 시 api-redis통신을 한번씩 하려고 했지만
누가봐도 좋은 방법은 아니였기 때문에 사용자가 처음 클라이언트 접속시 일괄적으로
redis key/value을 전부 가져와 window.localstorage 일괄 저장하는 방법으로 해결 했다.

_app.tsx에서 ios 웹뷰에서 보내준 디바이스UUID를 서버사이드에서 가져오자

_app.tsx

function App({ Component, pageProps,  deviceId }: Props) {
const { localstorageBindLoading } = useIOSFirstSetLocalStorage(deviceId);

return {
        {!localstorageLoading && (
           <Layout >
                <Component {...pageProps} />
           </Layout>
        )}
    }
  }

window.localstorage로 일괄 저장하는 useIOSFirstSetLocalStorage 추상화 시킨 커스텀훅

export const useIOSFirstSetLocalStorage = deviceId => {
  const [localstorageLoading, startLocalStorage] = useState(false);
  const isMount = global.window && window.localStorage.getItem('isSuccess');
  
  const setIosLocalStorage = async () => {
    const result = await cacheLocalstorage(deviceId);
    if (empty(result)) {
      Object.entries({ ...result, isSuccess: true }).forEach(([key, value]) => {
        localStorage.setItem(key, JSON.stringify(value));
      });
    }
    return startLocalStorage(false);
  };
  
  useEffect(() => {
    if (isIOS && !empty(isMount)) {
      startLocalStorage(true);
      localStorage.setItem('deviceId', deviceId);
      setIosLocalStorage();
    }
  }, [isIOS, isMount]);
  return { localstorageLoading };
};

레디스에서 모든 key/value 리스트를 받아오면 객체형식으로 받아오기 때문에
Object.entries로 배열 정규화를 시킨후 반복문을 이용해서 순차적으로 localstorage로 저장하는 구조이다

매번 page로드시 _app.tsx에서 해당 훅을 실행시키면 안되기 때문에 isSuccess플래그를 추가로 넣어 처음 웹뷰 접속시에만 해당 로직이 돌아가도록 하였다

_app.tsx에 존재하는 Layout컴포넌트에서 팝업이 이루어질경우 useIOSFirstSetLocalStorage처리하는과정에서 localstorage를 사용하지 못한다.
그래서 처리하는동안 localstorageLoading을 이용하여 처리가 완료되면 Layout으로 넘어가도록 하였다

문제을 해결하였지만

로컬스토리지를 저장할때마다 api통신을 하는것이 계속 걸려서 클라이언트에서 레디스 접속하여 pub/sub 하는 라이브러리를 찾던 중
ioredis를 알게 되었고 추후에 ioredis를 활용해서 node와 api통신 없이 처리하는 방법으로 수정을 해야 할 거 같습니다

감사합니다

profile
안녕하세요

0개의 댓글