Mobile

[Flutter] WebView의 Local Storage 주입

민갱스터 2024. 11. 13. 15:33

문제 상황

원래 각자 dev(fe) 서버에서 쓰는 테스트용 본인 계정이 있는데,
나는 전자결재 모듈을 개발해야 했기 때문에
테스트를 위해 기안자, 결재자 최소 두 명 이상의 계정이 필요했다.
 
다만 나는 ta 담당자였기 때문에 admin 계정까지 가지고 있었어서 총 두 개의 계정이 있긴 했다.

하지만 멀티 테넌트 방식을 이용하고 있기 때문에
admin 계정(qa 테넌트)과 개인 계정(개발 테넌트)의 테넌트가 달라서 같이 쓸 수 없는 상황이었다.
 


어차피 내 개인 계정도 관리자 권한이 있기 때문에 모바일 테스트용 신규 사용자 계정을 생성했었다.
 

그런데 어느날 우리 회사 슬랙 전체방에 이런 메세지가 올라왔다.

 
해당 계정으로 회사명이 없는 채 로그가 남아서 확인차 말씀해주신 것이라고 하셨다.
 



분명 이 계정에 회사 연결도 해놨는데 왜 회사명이 없다는거지?
모바일 아직 개발중이라서 뭔가 연동이 덜 되었나?
하면서 원인을 분석하기 시작했다.
 


전달 받은 바로는 소켓 서버 관련 몇몇 상황에 대해서 에러가 발생하는 것을 발견했고,
각각의 케이스의 원인을 알아내어 해결을 해두고 싶었는데
 
마침 특정 에러에 대한 로그로 최우식 계정이 여러 번 등장해서 언급한거라고 하셨다.
 


원인 분석 : 소켓 서버와의 연관성

모바일 구현에서 소켓 관련해서 뭐 빼먹은게 있는지 확인했는데,
 
1) 알림 목록 화면
소켓 서버와 바로 연동하는 것이 아닌 api로 알림 목록을 받아와서 그대로 보여주는 것일 뿐이다.
따라서 소켓과 직접적인 연관 x

 
 
2) 소켓 서버와 직접적 연동을 했는지
이 당시엔 모바일 푸시알림과 관련된 서버는 구현이 안된 상태였고

웹에 대한 소켓 서버와도 바로 연결을 한 적은 없다. 
 
3) 개발 중인 전자결재 모듈의 api나 다른 api 연동에서 문제가 있는지
jwt access 토큰값을 포함하여 헤더에 필요한 값들만 입력해서 api 요청을 하고 응답을 받는 방식에
에러 처리도 해두어서 별 문제가 없다. 소켓서버와도 크게 연관은 없다.
 
 
이런 식으로 의심 가는 부분들을 확인 해봤지만 문제는 없었다.
 


원인 분석 : 그라파나

일단은, 동일한 계정으로 웹에서 접속해서 사용할 땐 다 괜찮은데 📌모바일만 문제였다.


 
그런데 웹에서 괜찮다면 계정 자체를 잘못 생성한 것은 아닌 것이고 
일단 모바일에서 개발 중인 모듈인 전자결재가 유력 후보로 판단돼서

 
그라파나에 소켓 로그를 띄워두고 실시간으로 로그를 확인하며 원인을 분석하고자 했다.

모바일 전자결재 단계별로 실행해봤으나 아무 문제가 없었다.
 
그래서 다른 계정들로도 뭐가 다른지 테스트 해보려고 다른 계정으로 전환하자마자

 

그 문제의 에러가 나타났다!!!! 

 
 
로그인 자체가 문제인 것 같아서 계속 다른 계정 전환하면서 로그인했더니
로그인 할 때마다 에러 로그가 남았다.
 


근본적인 원인 : Web Local Storage

근데 모바일 로그인도 똑같이 인증 api 요청과 응답으로 하는 것이라
어디가 문제인지 좀 더 구체적인 원인을 찾아야했다.
 
여기서 우리 회사가 모바일 개발을 어떤식으로 하는지 얘기를 해야하는데
 
가장 핵심 기능인 전자결재 기능 부분만 플러터로 자체적으로 개발하고
나머지 부분은 기존에 출시된 웹에 대한 모바일 뷰를 만들어서 웹 뷰로 띄우기로 했다.
 
➡️ 전자결재를 웹뷰로 띄우지 않는 이유는 푸시알림과 전체적인 플로우도 연관이 있어서 그렇다.
 
그래서 크게 로그인 + 전자결재 + 알림이 플러터에서 dart로 구현하는거고 나머지는 웹뷰인데,

웹에서의 로그인과 플러터에서의 로그인을 통합을 시켜야하는 이슈를

1️⃣ 쿠키 주입

2️⃣ 웹 뷰는 모바일 기기로만 접근 가능하게

3️⃣ 웹 뷰는 플러터 경로 외에 다른 경로로 접근 못하게

처리하는 방식으로 해결했었다.
 

 
 
위의 방식으로 해결하는 과정에서 웹과 앱 번갈아가면서 테스트하다가 발견한 것이 있는데
로컬 스토리지에 프로필 관련 정보(회사 프로필 + 사용자 프로필)가 저장되어있는 것을 봤었다.


난 이것을 홈 화면 좌측 상단에 회사 프로필, 좌측 하단에 사용자 프로필이 있어서 그걸 위한 것이라고 생각했다.
모바일에서의 프로필은 웹뷰를 감싸는 주체인 플러터가 보여주는 것이어서 웹뷰에는 해당 정보를 주입하지 않았었다.
 
근데 로그인이 원인인 것이라면 웹과 다른 부분은 이 부분이기 때문에
해당 모듈에 각각의 파트 담당자분과 얘기를 나눠보니 원인을 명확히 알게 되었다.
 
로그인도 잘 되고, 다른 기능들도 문제 없이 잘 사용되기 때문에 넘어갔었는데,
알고보니 프로필 정보 중 하나인 줄 알았던 회사 코드 값이
소켓 서버와 사용자가 접속한 시점에 맺는 커넥션에 필요한 값이었다.
 
연결이 생겨야 알림을 줄 수 있어서 에러 처리가 되어있었는데 해당 모듈을 몰랐기에

그 부분을 넘겨서 그랬던 것이었다.
 
 
원래의 내 추측(단순히 프로필 표시용)과 같은 이유로 생각했을 땐

이런 사소한걸 하나씩 보고하는게 맞나 긴가민가 했었다.

 

하지만 이렇게 생각치도 못한 부분은 웹의 모듈 담당자 분이 말씀해주지 않으시면 알 방법이 없다.
 
그래서 이런 경우가 또 있을 수 있으니, 비슷한 상황이 생긴다면

모듈 담당자분께 정확히 어떤 의도로 쓰이는 것인지 확인이 필요함을 깨닫게 되었다.
 
 
결론적으로는 위의 과정으로 문제의 원인을 알게 되었으니 해결은 금방할 수 있었다!


해결

로컬 스토리지에 회사 코드값을 주입하는 함수(_setLocalStorage)를 만들고 
웹뷰 컨트롤러에 페이지 시작하자마자 실행하여 회사코드를 주입하도록 했다.

@override
  void initState() {
    super.initState();
    _webControllers = List.generate(
      _urls.length,
          (index) => WebViewController()
        ..setJavaScriptMode(JavaScriptMode.unrestricted)
        ..addJavaScriptChannel(
          'Flutter',
          onMessageReceived: (JavaScriptMessage message) {
          },
        )
        ..setNavigationDelegate(
          NavigationDelegate(
            onPageStarted: (String url){
              _setLocalStorage();  // 이 부분 추가
              _setJwtCookie();
            },
            onUrlChange: (UrlChange change) {
              setState(() {
                _currentUrl = change.url!;
              });
            },
            onPageFinished: (String url) {
            },
          ),
        )
        ..loadRequest(Uri.parse(_urls[index])),);
    _controller = _webControllers[_selectedIndex];
    fetchMypage();
    fetchAvatar();
    fetchAlertListNotConf();
  }
Future<void> _setLocalStorage() async {
    final savedCompany = await storage.read(key: 'company');
    if (savedCompany != null && savedCompany.isNotEmpty) {
      if (_controller != null) {
        await _controller!.runJavaScript("window.localStorage.setItem('company', '$savedCompany');");
      }
    }
  }

 

await _controller!.runJavaScript("window.localStorage.setItem('company', '$savedCompany');");

이 부분이 주입하는 부분이다.
 
 
쿠키 주입의 경우도 자바스크립트로 주입 가능하고 아래와 같이 이런식으로 직접 set을 하는 것도 가능하다

cookieManager.setCookie(
	WebViewCookie(
    		name: ...
    )
);

 
 

+) 여담

(처음에 말씀하신 회사명 없이 로그에 남는다는게 이거였다. alarm과 82 사이에 회사코드가 있어야 되는 것이었음.)



해결~~