Skip to content

Electron

Experimental

이 패키지는 실험 단계입니다. API가 예고 없이 변경될 수 있으며, 프로덕션 환경에서의 안정성이 보장되지 않습니다.

Electron용 Klleon AI 아바타 SDK (@klleon/sdk-electron)는 Web SDK와 동일한 API를 제공하며, 데스크톱 네이티브 기능(IPC, 클립보드, 트레이 등)과 통합됩니다.

SDK 요청

Electron SDK는 별도 요청을 통해 제공됩니다. 담당자에게 문의하여 SDK 패키지를 받으세요.

요구사항

항목버전
Electron>= 33
Node.js>= 20
agora-electron-sdk>= 4.3.2

설치

1. SDK 패키지 설치

제공받은 .tgz 파일을 프로젝트 루트에 두고 설치합니다.

bash
# SDK 설치
npm install ./klleon-sdk-electron-x.x.x.tgz

# Agora 네이티브 모듈 설치
npm install agora-electron-sdk

설치 후 package.json에 다음과 같이 추가됩니다:

json
{
  "dependencies": {
    "@klleon/sdk-electron": "file:klleon-sdk-electron-x.x.x.tgz",
    "agora-electron-sdk": "^4.3.2"
  }
}

2. SDK 업데이트

새 버전의 .tgz를 받으면 기존 파일을 교체하고 다시 설치합니다.

bash
npm install ./klleon-sdk-electron-x.x.x.tgz

3. Vite 설정

@klleon/sdk-electron은 순수 JavaScript 패키지(.node 바이너리 없음)이므로 Vite가 렌더러 번들에 포함시켜 tree shaking합니다. external에 넣으면 패키징된 앱에서 모듈을 찾지 못합니다.

C++ 바이너리가 포함된 네이티브 모듈(agora-electron-sdk)과 Electron 내장 모듈(electron)만 external로 설정합니다.

typescript
// vite.renderer.config.ts
export default defineConfig({
  optimizeDeps: {
    exclude: ["agora-electron-sdk", "electron"],
    // ❌ "@klleon/sdk-electron"은 여기 넣지 않기
  },
  build: {
    rollupOptions: {
      external: ["agora-electron-sdk", "electron"],
      // ❌ "@klleon/sdk-electron"은 여기 넣지 않기
    },
  },
});

ESM Tree Shaking

@klleon/sdk-electron은 ESM으로 빌드됩니다. Vite가 번들링하면서 사용하지 않는 코드를 자동으로 제거(tree shaking)합니다.

패키징 (DMG / EXE)

Electron Forge로 배포용 패키지를 만들 수 있습니다.

알려진 이슈

Electron Forge Vite 플러그인 버그

@electron-forge/plugin-vite는 패키징 시 node_modules를 출력에 포함하지 않습니다. Vite가 모든 코드를 번들링했다고 판단하지만, external로 설정한 네이티브 모듈(agora-electron-sdk)과 그 의존성은 번들에 포함되지 않아 런타임에 로드 실패합니다.

이 문제는 Electron Forge에 공식 등록된 버그입니다:

해결: forge.config.ts 설정

Forge 공식 훅 packageAfterCopynode_modules를 패키징 결과에 복사합니다. 복사 후 Forge가 자동으로 devDependencies를 제거(prune)합니다.

typescript
import type { ForgeConfig } from "@electron-forge/shared-types";
import { VitePlugin } from "@electron-forge/plugin-vite";
import { AutoUnpackNativesPlugin } from "@electron-forge/plugin-auto-unpack-natives";
import path from "path";
import fs from "fs";

const config: ForgeConfig = {
  packagerConfig: {
    asar: {
      // 네이티브 .node 파일은 asar 밖에서 로드해야 함
      unpack: "**/node_modules/{agora-electron-sdk,@anthropic-ai}/**",
    },
    // assets 디렉토리를 Contents/Resources/에 복사
    extraResource: ["./assets"],
  },
  hooks: {
    packageAfterCopy: async (_config, buildPath) => {
      // Forge Vite 플러그인 버그 (#3738) 워크어라운드
      // 복사 후 Forge가 자동으로 devDependencies prune 처리
      fs.cpSync(
        path.join(__dirname, "node_modules"),
        path.join(buildPath, "node_modules"),
        { recursive: true },
      );
    },
  },
  plugins: [
    // 네이티브 .node 파일을 asar에서 자동 추출
    new AutoUnpackNativesPlugin({}),
    new VitePlugin({
      build: [
        { entry: "electron/main.ts", config: "vite.main.config.ts", target: "main" },
        { entry: "electron/preload.ts", config: "vite.preload.config.ts", target: "preload" },
      ],
      renderer: [
        { name: "main_window", config: "vite.renderer.config.ts" },
      ],
    }),
  ],
  makers: [
    { name: "@electron-forge/maker-dmg", config: {} },
    { name: "@electron-forge/maker-squirrel", config: {} },
    { name: "@electron-forge/maker-deb", config: {} },
  ],
};

export default config;

빌드 명령어

bash
# 개발
npm run dev          # electron-forge start (HMR)

# 패키징
npm run make         # DMG/EXE/DEB 생성

API

KlleonSDK (싱글톤)

typescript
import { KlleonSDK } from "@klleon/sdk-electron";
분류메서드설명
생명주기init(options)SDK 초기화 및 연결
destroy()SDK 종료 및 리소스 해제
콜백onSignal(cb)서버 시그널 수신
onStatus(cb)SDK 상태 변경
onError(cb)에러 발생
텍스트sendMessage(text)텍스트 전송 (LLM 응답 트리거)
speak(text)텍스트 에코 (아바타가 그대로 읽음)
오디오 에코sendSpeakAudio(base64)Base64 오디오 전송
endSpeakAudio()오디오 에코 종료
음성startListening()음성 입력 시작 (Push-to-Talk)
endListening()음성 입력 종료
cancelListening()음성 입력 취소
아바타stopSpeaking()아바타 발화 중지
setVolume(0~100)아바타 음량 조절
비디오setContainer(container)비디오 렌더링 컨테이너 설정 (<avatar-container> 사용 시 자동)
조회getVersion()SDK 버전
getStatus()현재 상태
clearMessages()메시지 초기화

비디오 렌더링

<avatar-container> (권장)

HTML에 <avatar-container>를 넣으면 SDK 연결 시 자동으로 비디오가 렌더링됩니다. 내부적으로 setContainer()를 호출하고, 원격 유저 합류 시 <agora-view>를 생성·삽입합니다.

html
<avatar-container></avatar-container>
Attribute기본값설명
fit"cover"비디오 fit 방식 (cover, contain, fill)
html
<avatar-container fit="contain"></avatar-container>

setContainer() (수동)

커스텀 엘리먼트 대신 직접 컨테이너를 지정할 수도 있습니다. init() 전에 호출해도 됩니다.

typescript
KlleonSDK.setContainer(document.getElementById("my-avatar")!);

agora-electron-sdk 참고

Agora 공식 문서에서는 macOS 패키징 시 심볼 충돌 방지를 위해 import 대신 require를 권장합니다. @klleon/sdk-electron은 내부적으로 new Function("return require(module)")을 사용하여 Vite 번들링을 우회하고 런타임에 네이티브 모듈을 로드합니다.

sdkState (반응형 상태)

Valtio proxy로 상태를 구독할 수 있습니다.

typescript
import { sdkState } from "@klleon/sdk-electron";
import { subscribe } from "valtio/vanilla";

subscribe(sdkState, () => {
  console.log("status:", sdkState.status);
  console.log("messages:", sdkState.messageList);
});

초기화 옵션

옵션타입기본값설명
sdk_keystring-SDK 인증 키 (필수)
avatar_idstring-아바타 ID (필수)
languageLanguageCode"ko_kr"TTS 언어 (ko_kr, en_us, ja_jp, id_id)
log_levelLogLevel"debug"로그 레벨
enable_microphonebooleantrue마이크 활성화
auto_sendbooleanfalsetrue: VAD 자동 응답, false: Push-to-Talk
stt_onlybooleanfalsetrue: STT 결과만, false: STT + 아바타 응답

상태 흐름

IDLE → CONNECTING → SOCKET_CONNECTED → STREAMING_CONNECTED → CONNECTED_FINISH
상황흐름
정상 종료destroy()IDLE
에러 발생자동 cleanup → IDLE
재연결onError + onStatus(IDLE) 수신 후 init() 재호출

에러 코드

코드설명
SOCKET_FAILEDinit 중 WebSocket 연결 실패
SOCKET_DISCONNECTED_UNEXPECTEDLY런타임 WebSocket 끊김
STREAMING_FAILEDinit 중 Agora 연결 실패
STREAMING_DISCONNECTED_UNEXPECTEDLY런타임 Agora 끊김
SERVER_ERROR서버 에러 시그널
WORKER_DISCONNECTED워커 종료 시그널

BrowserWindow 설정

agora-electron-sdk는 렌더러 프로세스에서 Node.js API를 사용합니다.

보안 참고

nodeIntegration: true + contextIsolation: false는 Electron 보안 권장사항에 반하지만, agora-electron-sdk가 렌더러에서 require()를 직접 사용하기 때문에 현재로서는 불가피합니다. Agora SDK가 contextIsolation을 지원하면 즉시 마이그레이션하세요.

typescript
const mainWindow = new BrowserWindow({
  webPreferences: {
    nodeIntegration: true,     // 필수: agora-electron-sdk가 require() 사용
    contextIsolation: false,   // 필수: 렌더러에서 Node.js API 접근
  },
});

Web SDK와의 차이

항목Web (sdk-klleon)Electron (sdk-electron)
Agoraagora-rtc-sdk-ngagora-electron-sdk
비디오 렌더링<video> track.play()<agora-view> (내부)
비디오 컨테이너<avatar-container> (Lit)<avatar-container> (Web Component)
모듈 로딩ESM importrequire() (Vite 우회)
디바이스 정보navigator.userAgentprocess.platform / process.arch
UILit 커스텀 엘리먼트HTML + Electron IPC
배포CDN (UMD)앱 패키징 (DMG/EXE/DEB)
패키징불필요Electron Forge