@inho-team/electron
v0.1.0
Published
Scoped desktop bridge for Electron apps
Downloads
281
Readme
@inho-team/electron
Electron 44 앱의 main, preload, renderer 사이에 범위를 제한한 데스크톱 기능을 연결합니다. 앱 정보, 허용한 HTTPS 사이트 열기, 앱 데이터 파일, 사용자 파일 선택, 딥링크를 제공합니다. Grid의 앱 정보 조회와 제한된 preload 패턴을 출발점으로 삼았으며, Grid의 AI 작업과 갱신·배포 설정은 포함하지 않습니다.
pnpm add @inho-team/electron electronmain 프로세스
installElectronKit은 창을 만든 뒤 화면을 로드하기 전에 호출합니다. frameOrigins는 IPC 호출을 허용하는 화면의 정확한 출처이며, framePathPrefixes는 허용하는 경로의 접두사입니다. /app은 /app과 /app/settings를 허용하지만 /application은 거절합니다. 두 값을 모두 지정해야 합니다. externalOrigins는 운영체제 브라우저에서 열 수 있는 사이트의 정확한 출처입니다. 앱이 사용하는 출처와 경로만 지정합니다.
const { app, BrowserWindow, dialog, ipcMain, shell } = require('electron');
const { installElectronKit } = require('@inho-team/electron/main');
const window = new BrowserWindow({
webPreferences: {
preload: require.resolve('@inho-team/electron/auto-preload'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
const kit = installElectronKit({
app, window, dialog, ipcMain, shell,
frameOrigins: ['myapp://app'],
framePathPrefixes: ['/index.html'],
externalOrigins: ['https://example.com'],
linkOrigins: ['https://example.com', 'myapp-link://open'],
});
await window.loadURL('myapp://app/index.html');
// 앱을 종료하거나 이 창의 브리지를 제거할 때 kit.dispose()를 호출합니다.앱은 myapp://app 같은 화면용 프로토콜을 등록하고 실제 HTML 파일을 제공해야 합니다. 패키지는 창, Content Security Policy, 권한, 프로토콜, 서명, 앱 갱신을 대신 설정하지 않습니다. auto-preload는 하나의 파일로 묶여 있어 sandbox를 켠 preload에서 사용할 수 있습니다. 기존 preload에 통합한다면 @inho-team/electron/preload의 exposeElectronKit을 앱의 preload 번들에 포함합니다. sandboxed preload에서 npm 패키지를 직접 require하는 방식은 사용하지 않습니다.
renderer
import { createElectronKit } from '@inho-team/electron';
const desktop = createElectronKit();
const info = await desktop.getInfo();
await desktop.openExternal('https://example.com/help');
await desktop.writeTextFile('drafts/note.txt', '안녕하세요');
const text = await desktop.readTextFile('drafts/note.txt');
await desktop.writeBase64File('images/thumbnail.png', pngBase64);
const image = await desktop.readBase64File('images/thumbnail.png');
await desktop.deleteFile('drafts/note.txt');
const files = await desktop.chooseFiles({ extensions: ['png', 'jpg'], limit: 3 });
// 각 파일은 { name, base64 }이며 사용자가 파일 선택창에서 직접 고른 파일만 읽습니다.
const stop = await desktop.onDeepLink(({ path }) => router.navigate(path));
// 화면의 수명주기가 끝날 때 stop()을 호출합니다.앱 데이터는 Electron의 userData/inho-electron-files에 저장됩니다. 경로는 상대 경로만 허용하며 기호 링크를 통과하지 않습니다. 파일당 최대 10 MiB를 읽거나 쓸 수 있습니다. 파일 선택은 한 번에 최대 10개, 합계 20 MiB까지 허용합니다. 파일 전체를 Base64 문자열로 전달하므로 큰 파일의 스트리밍과 임의 경로 접근에는 앱 전용 main 프로세스 기능이 필요합니다. 선택을 취소하면 빈 배열을 반환합니다. 파일 내용과 이름은 선택을 마친 renderer로 전달되므로, 앱은 해당 화면의 신뢰 경계를 유지해야 합니다.
딥링크 연결
패키지는 URL을 검증하고 화면 준비 전 전달된 링크를 보관합니다. 운영체제의 URL 이벤트 등록과 단일 인스턴스 정책은 앱이 소유합니다. macOS의 open-url은 ready 이전에 구독해야 하며, Windows와 Linux는 second-instance 및 시작 인수를 처리해야 합니다. 운영체제에 프로토콜을 등록한 패키징 결과에서 각 경로를 검증합니다.
const pendingUrls = [];
let kit;
app.on('open-url', (event, url) => {
event.preventDefault();
if (kit) kit.routeDeepLink(url);
else pendingUrls.push(url);
});
const ownsInstance = app.requestSingleInstanceLock();
if (!ownsInstance) app.quit();
else app.on('second-instance', (_event, argv) => {
for (const value of argv) kit?.routeDeepLink(value);
});
// 창 생성 후 installElectronKit의 반환값을 kit에 넣습니다.
for (const value of [...pendingUrls, ...process.argv]) kit.routeDeepLink(value);linkOrigins와 정확히 일치하는 출처의 링크만 화면에 전달합니다. onDeepLink를 등록하기 전 링크는 첫 구독 시 전달되며, 구독 해제 함수는 여러 번 호출해도 안전합니다. 제품의 로그인 복귀 URL과 라우팅 정책은 앱에서 별도로 검증합니다.
검증
pnpm check는 브리지의 송신자·경로·크기·링크 검사와 배포 tarball 내용을 검사합니다. macOS에서 pnpm desktop:smoke를 실행하면 독립 소비자가 tarball을 설치하고 실제 Electron 창에서 sandboxed preload, 파일과 딥링크를 확인합니다. Windows와 Linux의 프로토콜 등록 및 패키징은 각 소비 앱에서 별도로 검증해야 합니다.
