본문 바로가기

Electron의 app 정보를 얻기 위한 삽질기

기술적인 이야기/웹 개발 2022. 1. 30.
반응형

개인적으로 Electron으로 공부 겸 사이드 프로젝트를 시작하고 있었다. 그 과정에서 앱의 데이터를 데이터베이스에 저장하기 위해 몇 가지 정보를 찾아보다 데이터베이스 파일을 저장할 위치에 대해서 정해야 할 시기가 왔다. 즉 앱 데이터 저장 디렉터리를 알아내기 위함이 이번 여정(?) 목적이다.

참고로 사용한 Electron은 현시점에서 가장 최신 버전인 16.x 버전이다.

목표는 userData

방법은 구글링 과정으로 쉽게 알아내었다. Electron에서 제공하는 app 인스턴스의 getPath 메서드를 통해 여러 디렉터리 정보를 얻을 수 있고 이 중에 userData가 내 목적에 딱 맞는다는 것이었다.

참고로 appData도 있지만 이건 앱 전용 디렉터리가 아니라 이 앱 정보 디렉터리를 모아두는 디렉터리를 의미한다. userData는 여기에 앱 이름의 디렉터리가 뒤에 더 붙어있다고 보면 된다.

app 정보 구해보기

구글링을 통해 얻은 정보로 아래와 같이 코딩해봤다. 대충 현재 앱의 데이터 디렉터리를 콘솔에 찍어보는 코드다. Svelte 프레임워크를 쓰는 프로젝트였기 때문에 그냥 시작점 파일에서 script 부분에 아래와 같은 코드를 넣어 봤다.

const { app } = require('electron');
console.log(app.getPath('userData'));

그런데 실행시켜보니 이 코드가 오류가 난다.

require is not defined

require가 없다라니 좀 충격적인 오류였다. 자바스크립트 모듈화에 있어서 가장 핵심적인 키워드 아닌가?

어쨌든 이 문제의 해결은 간단했다. nodeIntegration을 켜고 contextIsolation을 꺼버리면 된다. BrowserWindow 인스턴스를 생성할 때 webPreferences에서 값을 세팅해주면 된다.

window = new BrowserWindow({
    ...
    webPreferences: {
        nodeIntegration: true,
        contextIsolation: false,
        ...
    }
});

이 설정을 하면 보안 약점이 생긴다고 하는데 다른 답은 없는 것 같았다.

다시 app 정보 구해보기

필요한 준비를 마친 것 같으니 다시 실행시켜 봤다.

이 삽질기를 쓰게 된 발단

Uncaught TypeError: Cannot read properties of undefined (reading 'getPath')

불행히도 아직 이 코드는 제대로 실행되지 않았다. 어이없게도 app.getPathundefined 였던 것이었다. 이 삽질기의 바탕이 된 이 문제는 해결이 가능한 걸까?

remote에서 구해보기

정보를 좀 찾아보니 Electron도 웹 개발에서 사용되는 backend와 frontend 개념과 비슷하게 백엔드 역할의 main과 프런트엔드 역할의 renderer로 구분되어 있다는 것을 알게 되었다. 그리고 renderer 프로세스에서는 electron.app이 아니라 electron.remote.app으로 접근해야 한다는 것을 알게 되었다.

그래서 위에서 작성했던 코드를 아래와 같이 수정했다.

const { remote } = require('electron');
console.log(remote.app.getPath('userData'));

자 이제 해결되었겠지?

...

불행히도 전혀 해결되지 않았다. 거의 비슷한 오류가 발생했는데 이번에는 remote.appundefined 였다.

뭐 어쩌라는 거지?

728x90

remote 모듈?

어떻게 찾아보다 보니 remote 모듈은 그냥은 못 쓰고 별도의 설정이 필요하다는 내용을 봤다. 설정에서 enableRemoteModule을 켜주면 된다는 설명과 함께 말이다.

window = new BrowserWindow({
    ...
    webPreferences: {
        nodeIntegration: true,
        contextIsolation: false,
        enableRemoteModule: true,  // <- 이 녀석
        ...
    }
});

짜잔~

그래서 해결이.... 안 되었다. 아.... 여전히 동일한 문제가 발생하고 있었다.

확인해보니 enableRemoteModule은 이제는 지원하지 않는 옵션이라고 한다. 내가 쓰는 버전이 최신 버전이라 이렇게 고생하고 있다는 생각이 들었다. 하지만 억지로 최신 버전을 사용하지 않으면 심신이 불안(?)해지는 내 성격 때문에 구버전에 눈을 돌리는 일은 아마 없을 것 같다.

어쩌다 보니 다른 방법도 찾았다. 아래처럼 remote 모듈 자체를 로드해서 사용할 수도 있다고 하는 내용이었다.

const remote = require('@electron/remote')

물론 해결이 안 되었다. 아예 해당 패키지가 없다는 새로운 오류를 경험하게 되었다. 아마도 이것도 이제는 지원하지 않는 건가 보다.

뭐 하여간 remote를 쓰는 것도 최신 버전에는 안 된다는 의미로 이해하면 될 것 같다. 희망이 사라져 가는 느낌이 들었다.

잘못된 패키지

어떤 글에서 잘못된 패키지를 로드하는 것 아닌가 하는 내용을 봤다. 이럴 때는 아래의 코드로 어떤 패키지가 로드되는지 확인이 가능하다고 한다.

console.log(require.resolve('electron'));

위 코드를 실행시켜보면 해당 패키지의 위치가 절대경로로 표시된다고 한다.

그런데 내 경우 위 코드를 실행시켜보면 아래와 같이 표시되었다.

electron

뭐지? 패키지가 있는 경로가 표시되어야 확인이 가능하다고 하는데? 이상하다?

어쨌든 이상한 경로인 것 같으니 해당 페이지에서 안내하는 대로 Electron 패키지를 몽땅 삭제했다.

npm uninstall electron
npm uninstall -g electron

이후 다시 패키지를 설치했다.

npm install

그리고 앱을 실행시켜보면 이제 해결이 짜잔!

...

안 된다. 여전히 안 된다. 똑같은 결과가 반복되었다. 여전히 resolve 결과에 electron이라고 표시되는 엉뚱한 상황을 겪을 뿐이었다.

좀 더 삽질을 해보니 renderer 프로세스에서는 이렇게 표시되는데 main 프로세스에서는 정상적으로 절대경로의 디렉터리가 표시되는 것을 확인했다. 하지만 이 기능은 renderer에서 필요하기 때문에 아무 의미 없었다. 단지 require 하는 패키지는 제대로 프로젝트 로컬에 설치된 것이 맞다는 것만 확인할 수 있었다.

혹시 Svelte의 문제인가?

결론부터 적자면 Svelte와는 관련이 없었다.

React를 이용해봐도 동일한 문제를 겪었다. SvelteKit을 사용해봐도 동일한 문제를 겪었다. Vue를 이용해봐도 동일한 문제를 겼었다. 별도의 UI 프레임워크 없이 할 때도 동일한 결과를 얻었다.

덕분에 뭐가 어쨌든 "최신 Electron에서는 안 되는 것 같다"는 결론을 내릴 수밖에 없었다.

반응형

마지막 희망 IPC

사실 Electron의 main과 renderer 프로세스로 분리된 구조와 이 사이의 통신에 관한 기초적인 내용을 알고 있다면 해결하는 방법이 하나 남아있다는 것을 알고 있을 것이다. 바로 원격 함수 호출 기능으로 유명한 IPC다. 개인적으론 굳이 복잡하게 이렇게 하지 않고도 할 수 있는 방법이 있을 거라 믿고 삽질을 해왔었는데 결국 이 최후의 방법을 시도해보기로 했다.

main 프로세스는 Electron이 구동될 때 진입점으로 사용하는 스크립트라고 생각하자. 위의 BrowserWindow 인스턴스를 생성하는 코드 말이다. 이 main 부분이 서버 역할을 할 수 있으며 그래서 ipcMain 모듈을 이용해 IPC로 원격 호출이 가능한 함수를 만들어 줄 수 있다.

ipcMain을 이용해 함수를 구현하는 방법은 동기와 비동기 방식이 있다. 아무래도 쓰기엔 동기 방식이 편하니 동기식으로 만들어보자.

const { ipcMain } = require("electron");
...
ipcMain.on('main-appPath', (event) => {
    event.returnValue = app.getPath('userData');
});

함수를 만들었으니 이걸 renderer에서 호출해보자. 비슷하게 ipcRenderer라는 모듈을 이용해 호출할 수 있다.

const { ipcRenderer } = require('electron');
console.log(ipcRenderer.sendSync('main-appPath'));

이렇게 하고 실행시켜보니 드디어 다음과 같은 결과를 얻을 수 있었다. 참고로 macOS 환경이다.

/Users/foobar/Library/Application Support/appname

드디어 원하는 데이터를 드디어 renderer에서 얻을 수 있게 되었다. 결국 하기 싫던 최후의 방법으로만 해결이 되다니 슬프다.

IPC를 통한 원격 함수 호출에 대해서는 더 설명할 것이 있겠지만 이 글의 목적과는 부합하지 않으니 이 항목은 여기까지만 정리한다. 목표는 달성했으니까.

결론

위 한 줄의 문자열 데이터를 얻기 위해 약 2주 간의 삽질을 했다. 물론 2주 내내 한 것은 아니고 중간에 Electron 욕하며 잠깐 쉬다가 다시 삽질하는 식으로 해서 문제 해결에 총 2주라는 시간이 흘러 있었다는 이야기일 뿐이지만 하여간 2주나 걸렸다는 건 사실이다.

그래서 힘든 문제가 해결되어서 후련한가?

그냥 많이 아쉽다. 왜 쉬운 길을 다 막아 놓은 것일까? 아니면 내가 모르는 다른 방법이 있는 것일까? 근데 공식 매뉴얼에도 나와있는 내용이 왜 나는 안 되는 것일까? 이러니 보람이란 걸 느낄 세가 없었다. 내 시간 돌려줘!

어쨌든 결론을 내보자.

용도에 맞게 만들어둔 도구를 쓰자.

Electron도 버전이 올라가면서 많이 변화하고 있나 보다. "나 발전하고 있다"라는 티를 내려고 하위 호환성을 이렇게까지 깨 먹는 수준의 업데이트를 하는지도 모르겠다.

관련된 글들

 

Electron + Svelte로 데스크톱 앱 개발 시작해보기

개인 프로젝트를 여러 방식으로 해보다 Electron을 이용해볼까 하는 약간의 욕심이 생겼다. 그래서 공부를 하던 중 웹 UI 프레임워크를 알아두면 좋을 것 같아서 찾아보다 Svlete의 평이 좋을 것을

seorenn.tistory.com

 

Electron에서 iframe 사용하기(?)

Electron에서 iframe 태그를 사용할...수 없다. 이 무슨 시작부터 글 제목이랑 반대되는 이야기를 하는 건가 싶을 텐데, 사실 Electron이 보안상의 문제로 iframe 을 이용한 외부 사이트 접근을 기본적으

seorenn.tistory.com

 

Electron에서 타이틀 바 숨기기 및 창 이동 문제

최근의 macOS 앱들은 전통적인 타이틀 바 대신 타이틀 바 영역까지 툴바나 콘텐츠로 꽉꽉 채우는 것이 유행인 것 같다. 개인적으로도 이런 디자인이 유려하고 쓸모가 많은 것 같아 선호하기도 한

seorenn.tistory.com

728x90
반응형

댓글