으로

데스크탑 어플리케이션

만들어본 이야기

Electron?

Electron은

Electron을 선택한 이유

  • 적은 비용으로 멀티 플랫폼을 지원하는 앱 개발 가능
    • macOS, Windows, Linux
  • 웹 기술로 개발 - HTML, CSS, JavaScript
  • 크로스 브라우징 지원을 위한 코딩 불필요
  • 활성화된 커뮤니티
  • 문서화가 잘 되어 있음

최신 기술 사용 가능

  • 비교적 최근 버전의 Chromium을 탑재
    • ​Chromium 업데이트에 빠르게 대응
  • Chromium 58.0.3029 (@Electron 1.7.5)
    • ES6 커버리지: 97%
    • ES2016+ 커버리지: 81%
  • Node 7.9.0
  • V8 5.8.283.38

커버리지 자료: https://kangax.github.io/compat-table/es6/

지원 플랫폼

  • macOS 10.9+
  • Windows 7+ (x86 / x64)
  • Linux (x86 / x64)
    • ​Ubuntu 12.04+
    • Fedora 21+
    • Debian 8

Electron으로 만들어진 앱들

More apps in https://electron.atom.io/apps/

간보기

Quickstart

  • Electron API 데모
    • https://github.com/electron/electron-api-demos
$ git clone https://github.com/electron/electron-api-demos
$ cd electron-api-demos
$ npm install
$ npm start

REPL

$ electron --interactive
> const electron = require('electron');
> const win = new electron.BrowserWindow({width: 800, height: 600});
> win.loadURL('https://m.naver.com');

 Windows에서는 사용 불가 :-(

프로세스 구조

Renderer

Main

패키징

수동 빌드

  • 모든 소스 및 리소스를 ASAR로 묶기
    • 묶은 app.asar 파일을 resources 디렉토리로 이동
  • 앱 아이콘과 정보를 수정
    • macOS - Info.plist 파일 수정
    • Windows - electron.exe 를 rcedit로 수정

 

=> 실행 가능한 Electron 앱 생성

electron-builder

  • 크로스 플랫폼 빌드 툴
  • 빌드 과정에서 코드사인 자동 처리 (macOS, Windows)
  • 자동 업데이트 시 필요한 메타데이터 생성
  • 다양한 패키지 형태 지원
    • dmg, pkg, mas, deb, rpm, apk, nsis, portable, Squirrel.Windows, AppX

macOS 코드사인 

  • 애플 개발자 센터에서 인증서를 받아 키체인에 추가
  • 인증서에 코드사인 권한 추가
  • 인증서명을 CSC_NAME 환경변수로 등록
  • 등록된 환경변수를 바탕으로 electron-builder가 코드사인을 자동 수행

Windows 코드사인

  • 임의의 CA로부터 코드사인용 인증서 구매
  • 인증서 관리자에 추가
  • signtools 로 실행파일과 DLL에 서명
    • signtools는 Visual Studio와 Windows SDK에 포함

EV 인증서

  • EV 인증서 미사용 시 설치 과정에서 SmartScreen Filter의 경고 표시됨
  • 경고를 해제하려면 충분한 평판 점수가 쌓여야 함
  • 매 새로운 버전마다 평판 점수가 초기화됨

소스코드 보호

zeit/pkg (former EncloseJS)

  • V8 내부 컴파일러를 이용해 JavaScript 코드를 바이너리 스냅샷으로 변환
  • 단독 실행 가능한 바이너리를 얻을 수 있음
  • 소스코드가 노출되지 않음
PROJECT_ROOT
├─node_modules
└─src
    ├─client
    ├─electron
    └─server
PROJECT_ROOT
├─node_modules
└─output
    ├─client
    ├─electron
    └─server.exe

단점(?)

  • 외부 파일 경로 접근법이 달라짐
  • error stack trace에 라인 정보가 제거되어 추적에 어려움 발생

Auto Update

내장 autoUpdater의 제약

  • 업데이트 체크만으로도 다운로드가 자동 개시됨
  • 업데이트 다운로드 진척을 알 수 없음
  • 중간에 취소할 수 없음

업데이트 중개 모듈 추가

  • 다운로드 진척 이벤트 제공
  • 다운로드된 파일의 체크섬 검증 기능 추가
  • 내부에 HTTP서버를 띄워 autoUpdater에게 업데이트 파일 제공

1. 업데이트 체크

2. 다운로드

3. 내장 업데이트 모듈에 전달

4. 설치 및 재시작

electron-updater

  • 단계적 공개 기능 제공
  • 다운로드 진척 이벤트 제공
  • electron-builder와 잘 통합되어 있음

Platform limitations and workarounds

Menu is read only

  • 메뉴의 항목을 동적으로 추가하거나 레이블을 변경할 수 없음
  • 변경하려면 메뉴 전체를 다시 빌드해서 교체해야 함

Clipboard

  • 지원 포맷: Text, HTML, Image 
  • 제약
    • 커스텀 클립보드 포맷을 지원하지 않음 
    • 텍스트로 클립보드에 넣으면 다른 어플리케이션에 이상한 문자열이 붙여지게 됨
  • 대책
    • 가상 클립보드 + HTML 타입 + 메타태그에 클립보드 ID
    • 어플리케이션 종료 시 가상 클립보드를 디스크에 저장

Clipboard

  • 1.7.5 부터 clipboard 모듈이 Buffer 타입을 지원
    • 이젠 꼼수 쓸 필요 없음

Proxy

  • NodeJS는 시스템 proxy를 자동으로 사용하지 않음
  • Electron은 독자적인 net 모듈을 제공
    • Chromium의 내장 네트워크 라이브러리
    • 시스템 proxy설정을 가져옴
    • 프록시 인증 (basic, digest, NTLM, Kerberos..) 을 지원
    • 트래픽 모니터링 프록시 지원 (like Fiddler)

ProtoPie 아키텍처 설계와 고려사항

원하는 건: 프로세스 구분이 없는 듯한 API

  • 모든 코드가 한 프로세스 (renderer)에 있는 듯한 코드 스타일
  • API 사용 시 항상 프로세스를 떠올리지 않았으면
undoButton.addEventListener('click', e => {
    protopie.history.undo();
});

protopie.history.on('undostackchange', (undoCount, redoCount) => {
    if (undoCount === 0) {
        undoButton.disabled = true;
    }
});

제약

  • 모든 로직은 서버에 둬야 함
    • 클라이언트 코드는 EncloseJS로 패키징할 수 없으므로
  • 소스코드는 보호할 수 있지만 대신 복잡성이 증가

Transparent server API

  • Add ipc tunnel between client and server
    • Client can send event to server

Virtual API

  • server는 실제 API로부터 메타데이터를 추출
    • decorator, decorator-metadata 기반
  • client는 메타데이터로부터 server API를 재구축
  • client에 대한 API 공개 범위를 조절할 수 있음
export class CommandHistory {
    @CallbackApi
    public undo() {
    }

    @ListenerApi
    public on(eventType, callback) {
    }    
}
const commandHistory = new CommandHistory();
virtualApi.addService('history', commandHistory);
const server = new VirtualApiClient(tunnelClient);
undoButton.addEventListener("click", () => {
    server.history.undo();
});

Command History

  • 모든 모델 변경은 완전히 되돌리거나 재실행할 수 있어야 함
    • Undo / Redo / Copy / Paste
  • 모델을 변경하기 위한 별도의 Command 생성
    • 모델 변경은 Command를 통해서만 할 수 있도록 수정 API 노출 차단
  • 멱등성이 중요함
    • Undo시 모든 실행 순서는 반대 순서로 되돌리는 게 좋음
    • Layer tree는 가장 위에서부터 수정하고, 가장 아래서부터 undo 해야 멱등성을 유지하기 편리함

Command History

  • 모든 모델 변경은 항상 이벤트로 클라이언트에 전달됨
    • 클라이언트는 최대한 Passive View가 되도록 구성
    • 상태를 갖지 않아야 함
export class Layer {
    @Watch
    x = 0;

    @Watch
    y = 0;
}
server.command("layer_edit", layerId, {x: 10, y:20});
server.layers.on('change', (layer, prop, newValue, oldValue) => {
    this.element.style.left = layer.x + 'px';
    this.element.style.top = layer.y + 'px';
});
export class CommandHistory {
    @CallbackApi
    undo() { ... }

    @CallbackApi
    redo() { ... }

    @ListenerApi
    on(eventType, callback) { ... }
}
this.apiServer.addService('history', new CommandHistory(context));
server.history.on('undostackchange', (undoCount, redoCount) => {
   ...
});
server.history.undo();

Context

  • 다중 윈도우 어플리케이션은 최소 2종류의 context를 가짐
    • 어플리케이션 전체 Context
      • e.g., DeviceConnectionManager, LicenseManager
    • 윈도우별 Context(ProtoPie에서는 Authoring Context라 부름)
      • e.g., ModelRoot, SelectionManager, CommandHistory

Writing Desktop application with Electron

By gloridea

Writing Desktop application with Electron

  • 1,138