일렉트론(Electron) ipcMain: 메인 프로세스와 렌더러 프로세스 통신

일렉트론(Electron) 메인 프로세스와 렌더러 프로세스

이전 포스트 일렉트론(Electron) 프레임워크: 웹 기술로 GUI 제작 하기 | Node.js 를 통해 저희는 웹 기반 기술로 데스크톱 프로그램을 제작할 수 있음을 확인 하였다.

오늘은 일렉트론에서 화면을 구성하는 영역인 렌더러 프로세스메인 프로세스에 개념에 대해 정리한다.

메인 프로세스

웹 사이트라면 백엔드 코드 역할을 해주는 영역이다. 처음 일렉트론이 실행되는 main.js 를 말하며 애플리케이션을 시작, 종료, GUI 창 생성, 시스템 리소스 접근 (파일) 등의 역할을 한다.

이 부분에서 기존 웹 애플리케이션을 제작할 때 서버 단에 사용하던 코드를 작성한다 생각하면 편하다. 여러 Node.js 모듈들과 호환되며 백엔드 로직을 구성 한다.

렌더러 프로세스

일렉트론에서 프론트엔드 영역을 말한다. 화면을 구성하는 HTML, CSS, JavaScript 등의 코드 영역으로 화면을 구성하기 위한 내용을 작성한다.

ipcMain 을 이용한 두 프로세스 간의 통신 구현

ipcMain 은 메인, 렌더러 프로세스 간 통신을 위한 객체이다. 예를 들어 버튼을 눌렀을 때 렌더러 프로세스가 백단인 메인 프로세스에 데이터를 요청하는 동작을 구현할 때 사용된다.

기존의 일렉트론 구현을 위해 모듈에서 객체들을 가져올 때 ipcMain 도 함께 가져온다.

const { app, BrowserWindow, ipcMain } = require('electron');
JavaScript

프로세스간 통신 기초

프로세스간 통신을 위해 기초 코드는 다음과 같다. 먼저 일렉트론(Electron) 프레임워크: 웹 기술로 GUI 제작 하기 | Node.js 참고하여 기본적인 일렉트론 애플리케이션을 만든 후 관련 코드를 추가한 것이다.

main.js
const { app, BrowserWindow, ipcMain } = require('electron')

function createWindow () {
  // 일렉트론의 메인 창을 생성한다.
  let win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false // Electron 12 이상에서는 기본값이 true 이므로 예시를 위해 임시로 false 로 바꾼다.
    }
  })

  // 그리고 앱의 index.html을 로드한다.
  win.loadFile('index.html')
}

app.whenReady().then(createWindow)

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
});
JavaScript
  • require 로 ipcMain 을 가져왔는지 반드시 확인한다.
  • 일렉트론 메인 창을 생성할 때 contextIsolation: false 옵션이 있는지 반드시 확인한다.

렌더러 프로세스에서 보낸 메시지 수신

그 후 main.js 맨 아래에 다음과 같은 코드를 추가한다.

main.js
...

// 렌더러 프로세스에서 메시지 수신
ipcMain.on('request-data', (event, arg) => {
    console.log(arg);  // 렌더러 프로세스에서 전송된 메시지 출력
    event.reply('response-data', '정상적으로 통신 되었습니다.');
});
JavaScript
  • request-data: 첫 번째 인자인 request-data 은 이벤트 이름으로 렌더러 프로세스에서 호출하게 될 이벤트 이름을 지정한다.
  • event.reply(‘response-data’, ‘정상적으로 통신 되었습니다.’): 메시지를 정상적으로 수신하면 렌더러 프로세스 쪽으로 결과를 반환하는 코드이다.

위의 코드는 렌더러 프로세스(HTML 등 화면 영역) 에서 특정 코드로 인해 메시지를 보낼 때 메인 프로세스에서 동작하게 될 코드이다. (렌더러 프로세스 → 메인 프로세스 방향)

렌더러 프로세스에서 보낸 메시지 보내기

위의 main.js 로 메세지를 보내는 코드는 다음과 같이 작성한다.

<!DOCTYPE html>
<html>
<head>
    <title>첫 화면</title>
</head>
<body>
    <h1>Hello, World!</h1>
    <p>이 화면은 첫 화면 입니다.</p>
    <button id="testBtn">테스트 버튼</button>
    <script>
         const { ipcRenderer } = require('electron');

        // 버튼 클릭 이벤트 리스너
        document.getElementById('testBtn').addEventListener('click', () => {
            ipcRenderer.send('request-data', 'Requesting data...');
        });

        // 메인 프로세스로부터의 응답 수신
        ipcRenderer.on('response-data', (event, response) => {
            console.log(response);
            alert(response);
        });
    </script>
</body>
</html>
HTML
  • ipcRenderer.send(‘request-data’, ‘Requesting data…’): 첫 번째 인자에 main.js 와 같은 이벤트 명 ‘request-data’ 을 작성하고 보낼 데이터(Requesting data…)를 두 번째 인자에 작성 한다.
  • ipcRenderer.on(‘response-data’, (event, response) => {}): 메인 프로세스(main.js) 에서 reply 함수로 반환 시 동작할 코드 영역이다.

그 후 npm run 명령어로 애플리케이션을 실행해서 버튼을 누르면 다음과 같이 출력된다.

ipcMain 통신 결과

contextIsolation: true 에서 사용하는 방법

일렉트론(electron) 최신 버전에선 렌더러 프로세스에서 직접 일렉트론 API 를 사용하는 것을 보안 상 권장하지 않아 contextIsolation 옵션이 true 값이 기본 상태이다.

contextIsolation 이 true 일 때 렌더러와 메인 프로세스 사이에서 격리된 레이어를 제공 하게 되어 웹 콘텐츠에서 직접 Node.js API에 접근하는 것을 막아 낸다.

즉, 렌더러 프로세스 에선 일렉트론 관련 코드를 직접 사용 할 수 없고 중간에서 둘 사이를 통신하기 위한 preload.js 파일을 별도로 만들어 통신을 해야 한다.

preload 스크립트 작성

preload.js 파일을 만들어 아래와 같은 스크립트를 작성한다.

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld(
  'electronAPI', {
    sendData: (data) => ipcRenderer.send('request-data', data),
    receiveData: (callback) => ipcRenderer.on('response-data', (event, response) => callback(response))
  }
);
JavaScript
  • ‘electronAPI’ 부분에 코드를 호출할 메서드 명을 자유롭게 지정해 준다.
  • ES6 모듈을 사용 중 이여도 preload.js 는 CommonJS 방식의 코드(import 가 아닌 require 사용)로 작성해야 한다.
  • sendData 는 렌더러 프로세스 → 메인 프로세스 방향의 코드 이다.
  • receiveData 는 메인 프로세스 렌더러 프로세스 방향의 코드 이다.

작성 된 코드에 의해 contextBridge 객체의 API 가 이 채널을 만드는 역할을 해준다.

위의 코드가 작성 된 이후 렌더러 프로세스 영역에서 window 객체를 이용해 호출할 수 있게 된다. 이 때 exposeInMainWorld 메서드의 첫 번째 매개변수로 지정한 값이 메서드 명이 되고 오브젝트의 키들의 하위 메서드가 된다.

예를 들어 위의 코드에 렌더러 프로세스에서 window.electronAPI.sendData(‘전달할 데이터’) 의 코드로 호출 되어 사용된다는 의미다.

window.electronAPI.receiveData((data) => { // 메인 프로세스 → 렌더러 프로세스
    console.log(data);
    alert(data);
});

document.getElementById('testBtn').addEventListener('click', () => { // 렌더러 프로세스 → 메인 프로세스
    window.electronAPI.sendData('테스트');
});
JavaScript
  • 위의 코드는 testBtn 을 클릭할 시 window.electronAPI.sendData(‘테스트’); 를 호출하여 메인 프로세스로 데이터를 전송한다.
  • window.electronAPI.receiveData(() => {…}) 코드는 메인 프로세스에서 호출 시 동작하는 코드이다.

그리고 main.js 의 createWindow() 부분으로 돌아가 webPreferences 옵션에 preload 를 추가해 다음과 같이 파일의 경로를 지정해 준다.

const path = require('path'); //경로 설정하기 좋은 모듈 path도 추가
...

function createWindow() {
    let win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'),
            contextIsolation: true
        }
    });
    ...
}
JavaScript

이 코드가 버튼 클릭 후 위의 기존과 동일하게 동작하는지 확인한다.

메인 프로세스 → 렌더러 프로세스 통신

위의 코드에서 메인 프로세스 에서 event.reply 로 메시지를 수신 후 결과를 렌더러 프로세스 쪽에 보낼 수 있는데, 메시지를 수신한 event 가 발생하지 않은 상태에서 메인 프로세스에서 바로 렌더러 쪽에 메시지를 보내는 방법은 무엇일까?

예를 들어 메인 프로세스에서 3초마다 렌더러 프로세스 쪽에 메시지를 보내거나 처음 앱 실행 시 DB의 SQL 문이 끝나면 메시지를 보내고 싶은 경우가 있을 수 있다.

이를 위해선 new BrowserWindow 로 생성 된 객체에 webContents.send 메서드를 통해서 사용이 가능하다. 위의 코드에서 let win 변수에 브라우저에 대한 객체가 담긴 상태에서 다음과 같은 코드를 예시로 작성해 볼 수 있다.

main.js
...
function createWindow() {
    ...
    win.webContents.on('did-finish-load', async () => { // 화면의 데이터가 모두 로드 된 이후에 실행 되는 코드
        win.webContents.send('receiveData', 'send Message'); // 메인 프로세스 → 렌더러 프로세스로 메시지 전송
    })    
}

위의 코드는 처음 화면이 완전히 불러와 졌을 때 메인 프로세스 → 렌더러 프로세스로 메시지를 전송 하는 코드이다. ‘receiveData’ 의 이벤트 명은 preload 에서 자유롭게 추가, 변경 하고 렌더러 프로세스 영역에서 수신할 코드를 작성하면 된다.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x
목차
위로 스크롤