### Install Project Dependencies Source: https://github.com/bgpworks/boxhero-electron/blob/main/README.md Run this command to install all the necessary dependencies for the project. ```sh npm install ``` -------------------------------- ### Start Development Server Source: https://github.com/bgpworks/boxhero-electron/blob/main/README.md Use this script to start the development server and begin working on the application. ```sh npm run start ``` -------------------------------- ### Install New ESLint Packages Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Command to install the updated ESLint packages and related plugins required for the new configuration. ```bash # 1. 기존 패키지 제거 npm uninstall eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-import # 2. 새 패키지 설치 npm install -D eslint@9.39.2 typescript-eslint@8.50.0 eslint-plugin-import-x@latest npm install -D globals@latest @eslint/js@latest ``` -------------------------------- ### Execute Phase 1 upgrade commands Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to install updated versions of Husky, lint-staged, and Prettier, and re-initialize hooks. ```bash # 1. husky 9 설치 npm install -D husky@9.1.7 # 2. .husky 재초기화 rm -rf .husky npx husky init # 3. pre-commit hook 생성 echo "npx lint-staged" > .husky/pre-commit # 4. lint-staged 업그레이드 npm install -D lint-staged@15.5.2 # 5. prettier 업그레이드 npm install -D prettier@3.5.0 ``` -------------------------------- ### Vite Upgrade Commands Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to check available versions and install specific Vite versions. ```bash # 버전 확인 npm view vite versions --json | tail -10 # Vite 6.x 최신 (안전한 선택) npm install -D vite@^6.4.0 # 또는 Vite 7.x (릴리즈 시) npm install -D vite@7 ``` -------------------------------- ### Verify React 19 Upgrade Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to check TypeScript compilation and start the application to verify the React 19 upgrade. Includes functional testing points. ```bash # TypeScript 컴파일 npx tsc --noEmit # 앱 시작 npm start # 기능 테스트: # - TitleBar 렌더링 # - LoadingIndicator 애니메이션 # - 언어 전환 # - 윈도우 컨트롤 ``` -------------------------------- ### Configure Windows Local Signing Environment Variables Source: https://github.com/bgpworks/boxhero-electron/blob/main/README.md Set these environment variables for local code signing on Windows using Azure Trusted Signing. Ensure .NET runtime 8.0+ and signtool are installed. ```sh AZURE_CLIENT_ID="fill_here" AZURE_CLIENT_SECRET="fill_here" AZURE_TENANT_ID="fill_here" AZURE_CODE_SIGNING_DLIB="C:\path\to\Azure.CodeSigning.Dlib.dll" SIGNTOOL_PATH="C:\path\to\signtool.exe" ``` -------------------------------- ### Upgrade React and React DOM to Version 19 Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to uninstall old React type packages and install React and React DOM version 19.2.3. ```bash # 1. 타입 패키지 제거 npm uninstall @types/react @types/react-dom # 2. React 19 설치 npm install react@19.2.3 react-dom@19.2.3 ``` -------------------------------- ### Manage Application Auto-Updates with Updater Class Source: https://context7.com/bgpworks/boxhero-electron/llms.txt The Updater class handles automatic application updates, including checking, downloading, and installing. It supports platform-specific feed URLs and periodic checks. Ensure the feed URL is correctly configured for the target platform. ```typescript // src/updater.ts import { autoUpdater, dialog } from "electron"; import ms from "ms"; class Updater { static #instance?: Updater; private state: UpdateState = "pending"; static getInstance() { if (!this.#instance) { this.#instance = new Updater(); } return this.#instance; } // 업데이트 피드 URL 설정 (플랫폼별) public setFeedURL(baseURL: string) { switch (process.platform) { case "darwin": autoUpdater.setFeedURL({ serverType: "json", url: `${baseURL}/RELEASES.json`, }); break; case "win32": autoUpdater.setFeedURL({ url: baseURL }); break; } return this; } // 주기적 업데이트 확인 시작 public watch(updateInterval: ms.StringValue = "5 minutes") { this.stopWatch(); this.checkForUpdates(); this.intervalID = setInterval(() => { this.checkForUpdates(); }, ms(updateInterval)); return this; } // 업데이트 확인 중지 public stopWatch() { clearInterval(this.intervalID); return this; } // 수동 업데이트 확인 public checkForUpdates() { if (!["checking", "available"].includes(this.state)) { autoUpdater.checkForUpdates(); } return this; } // 업데이트 설치 및 재시작 public quitAndInstall() { autoUpdater.quitAndInstall(); return this; } // 업데이트 알림 초기화 public initAlarm() { autoUpdater.on("update-downloaded", (_, releaseNotes, releaseName) => { this.openUpdateAlarm(releaseName); }); return this; } } // 사용 예시 (initUpdater.ts) Updater.getInstance() .setLogger(log) .setFeedURL(`${FEED_BASE_URL}/darwin`) .initAlarm() .watch("5 minutes"); ``` -------------------------------- ### Initialize Application Lifecycle Source: https://context7.com/bgpworks/boxhero-electron/llms.txt The main entry point manages Electron lifecycle events, including desktop authentication, window creation, and cleanup tasks. ```typescript // src/main.ts import { app } from "electron"; import log from "electron-log"; import initialize from "./initialize"; import initDesktopAuth from "./initialize/initDesktopAuth"; import { BoxHeroWindow, windowManager } from "./window"; import { stopLabelPrinter } from "./labelPrinter"; function main() { // 로깅 초기화 log.initialize(); log.errorHandler.startCatching(); log.transports.file.level = "info"; // 데스크톱 인증 초기화 (app ready 전에 호출 필요) // false 반환 시 다른 인스턴스가 실행 중 if (!initDesktopAuth()) { return; } // 앱 준비 완료 시 초기화 및 메인 윈도우 생성 app.on("ready", async () => { await initialize(); windowManager.open(BoxHeroWindow); }); // 모든 윈도우 닫힘 처리 (macOS 제외) app.on("window-all-closed", () => { if (process.platform === "darwin") return; app.quit(); }); // macOS에서 앱 아이콘 클릭 시 윈도우 재생성 app.on("activate", (_, hasVisibleWindows) => { if (hasVisibleWindows) return; windowManager.open(BoxHeroWindow); }); // 앱 종료 전 라벨 프린터 정리 app.on("before-quit", () => { stopLabelPrinter(); }); } ``` -------------------------------- ### Package Application Source: https://github.com/bgpworks/boxhero-electron/blob/main/README.md Commands for packaging and preparing the application for distribution. ```sh # 실행가능한 app bundle만 생성 npm run package ``` ```sh # 설치파일 생성 npm run make ``` ```sh # 배포 npm run publish-app ``` -------------------------------- ### Execute Phase 2 upgrade commands Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to check for latest versions and upgrade i18next packages. ```bash # 버전 확인 npm view i18next versions --json | tail -20 npm view react-i18next versions --json | tail -20 # 업그레이드 npm install i18next@latest react-i18next@latest ``` -------------------------------- ### Verify Phase 2 upgrade Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Steps to verify internationalization functionality after the upgrade. ```bash npm start # 검증 항목: # 1. 메뉴 언어 표시 정상 여부 # 2. 언어 전환 기능 정상 여부 # 3. 콘솔 에러 없음 확인 ``` -------------------------------- ### Emergency Rollback Procedure Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Steps to restore the project state if an upgrade fails. ```bash # 1. package.json 복원 git checkout -- package.json package-lock.json # 2. 설정 파일 복원 git checkout -- .eslintrc.json .husky/ # 3. node_modules 재설치 rm -rf node_modules npm install # 4. 동작 확인 npm start ``` -------------------------------- ### Electron Version Migration Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands for upgrading Electron versions incrementally and verifying the build. ```bash # Electron 35 npm install -D electron@35 npm start # 테스트 후 다음 버전 # Electron 36 npm install -D electron@36 npm start # ...반복 # 최종 Electron 39 npm install -D electron@39.2.7 npm start npm run package npm run make ``` -------------------------------- ### Electron Forge Build and Deployment Commands Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Commands for managing the Electron application's build and deployment lifecycle using Electron Forge. ```bash # 개발 서버 실행 npm run start # 앱 패키징 (설치파일 없이 실행 가능한 번들) npm run package # 설치파일 생성 (DMG, EXE 등) npm run make # GitHub Releases 및 Cloudflare R2에 배포 npm run publish-app # 코드 린트 npm run lint ``` -------------------------------- ### Environment Variables for Build and Signing Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Configuration of environment variables for macOS and Windows signing, Azure Trusted Signing, and update feed URLs. ```bash # 환경 변수 설정 (.env) # macOS 서명/공증 APPLE_APP_BUNDLE_ID="io.boxhero.desktop" APPLE_CERTIFICATE_IDENTITY="Developer ID Application: ..." APPLE_API_KEY_ID="ABCD123456" APPLE_API_ISSUER="..." # Windows Azure Trusted Signing AZURE_CLIENT_ID="..." AZURE_CLIENT_SECRET="..." AZURE_TENANT_ID="..." AZURE_CODE_SIGNING_DLIB="C:\\path\\to\\Azure.CodeSigning.Dlib.dll" SIGNTOOL_PATH="C:\\path\\to\\signtool.exe" # 업데이트 피드 URL FEED_BASE_URL="https://updates.boxhero.io" # 개발 옵션 DEV_SKIP_SIGN=t # 코드 서명 스킵 DEV_USE_BETA_LANE=t # 베타 빌드 ``` -------------------------------- ### Verify Phase 1 upgrade Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to verify that lint-staged is correctly configured and operational. ```bash # lint-staged 동작 확인 git add -A git commit -m "test commit" --dry-run # 또는 직접 lint-staged 실행 npx lint-staged ``` -------------------------------- ### Electron Verification and Rollback Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Standard verification steps for each Electron version and rollback commands. ```bash # 1. 개발 모드 실행 npm start # 2. 기능 테스트 # - 윈도우 생성/크기조절/최소화/최대화 # - 메뉴 동작 # - IPC 통신 # - 네비게이션 (뒤로/앞으로) # - 업데이터 동작 # 3. 빌드 테스트 npm run package # 4. 설치파일 생성 npm run make ``` ```bash npm install -D electron@34.3.0 # 또는 마지막 성공 버전으로 npm install -D electron@35.x.x ``` -------------------------------- ### Vite Verification and Rollback Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands for testing the development server and build process, or rolling back to a previous version. ```bash npm start # 개발 서버 npm run package # 빌드 테스트 ``` ```bash npm install -D vite@6.2.0 ``` -------------------------------- ### TypeScript Upgrade and Verification Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to check TypeScript versions and verify the project build. ```bash npm view typescript versions --json | tail -10 ``` ```bash npm install -D typescript@latest ``` ```bash npx tsc --noEmit npm run lint npm start ``` -------------------------------- ### Rollback Phase 1 changes Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to revert dependencies and configuration files to their previous state. ```bash npm install -D husky@8.0.3 lint-staged@15.0.1 prettier@3.0.3 git checkout -- .husky/ git checkout -- package.json ``` -------------------------------- ### Check Vite Versions Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Command to view available Vite versions in the npm registry, useful for determining the latest stable or release candidate versions. ```bash npm view vite versions --json | tail -10 ``` -------------------------------- ### Rollback Phase 2 changes Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Command to revert i18next packages to their previous versions. ```bash npm install i18next@23.6.0 react-i18next@13.3.0 ``` -------------------------------- ### Manage Windows with WindowManager Source: https://context7.com/bgpworks/boxhero-electron/llms.txt A singleton class used to track, create, and query application windows by type. ```typescript // src/window.ts import { BrowserWindow } from "electron"; class WindowManager { private registry: Map = new Map(); // 특정 타입의 윈도우 목록 조회 getWindows(typeClass?: T): InstanceType[] { if (!typeClass) { return [...this.registry.values()].flat() as InstanceType[]; } const windows = this.registry.get(typeClass) ?? []; return windows as InstanceType[]; } // 새 윈도우 생성 및 등록 open( typeClass: T, ...args: ConstructorParameters ) { const newWindow = new typeClass(...args); this.register(typeClass, newWindow); newWindow.once("closed", () => { this.unregister(typeClass, newWindow); }); return newWindow; } // 포커스된 윈도우 조회 getFocusedWindow( typeClass?: T ): InstanceType | undefined { const windows = this.getWindows(typeClass); return windows.find((window) => window.isFocused()); } // 현재 열린 윈도우 수 조회 getSize(typeClass?: ViteWindowConstructor) { return this.getWindows(typeClass).length; } } export const windowManager = new WindowManager(); // 사용 예시 windowManager.open(BoxHeroWindow); const focusedWindow = windowManager.getFocusedWindow(BoxHeroWindow); const windowCount = windowManager.getSize(BoxHeroWindow); ``` -------------------------------- ### Update package.json prepare script for Husky 9 Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Modify the prepare script in package.json to support the simplified Husky 9 initialization. ```json // Before "prepare": "husky install" // After (husky 9) "prepare": "husky" ``` -------------------------------- ### Expose IPC API in Preload Script Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Uses contextBridge to expose a secure API to the renderer process via window.electronAPI. ```typescript // src/preload.ts import { contextBridge, ipcRenderer } from "electron"; const api: electronAPI = { platform: process.platform, // 네비게이션 API navigation: { goBack: () => ipcRenderer.invoke("history/go-back"), goForward: () => ipcRenderer.invoke("history/go-forward"), reload: () => ipcRenderer.invoke("history/reload"), onSyncNav: (callback) => ipcRenderer.on("sync/nav-stat", callback), offSyncNav: (callback) => ipcRenderer.off("sync/nav-stat", callback), }, // 윈도우 컨트롤 API window: { toggleMaximize: () => ipcRenderer.invoke("window/toggle-maximize"), minimize: () => ipcRenderer.invoke("window/minimize"), maximize: () => ipcRenderer.invoke("window/maximize"), close: () => ipcRenderer.invoke("window/close"), getWindowStat: () => ipcRenderer.invoke("window/get-stat"), onSyncWindowStat: (callback) => ipcRenderer.on("sync/window-stat", callback), offSyncWindowStat: (callback) => ipcRenderer.off("sync/window-stat", callback), }, // 로딩 상태 API loading: { onSyncLoading: (callback) => ipcRenderer.on("sync/loading", callback), offSyncLoading: (callback) => ipcRenderer.off("sync/loading", callback), }, // 앱 API app: { openMainMenu: () => ipcRenderer.invoke("app/open-main-menu"), openExternal: (url: string) => ipcRenderer.invoke("app/open-external-link", url), getLocale: (): Promise => ipcRenderer.invoke("app/get-app-locale"), }, }; contextBridge.exposeInMainWorld("electronAPI", api); ``` -------------------------------- ### Handle Navigation IPC Requests Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Main process handlers for browser history navigation actions. ```typescript // src/initialize/initNavigationIPC.ts import { ipcMain } from "electron"; import { BoxHeroWindow, windowManager } from "../window"; const initNavigationIPC = () => { // 뒤로 가기 ipcMain.handle("history/go-back", () => { const focusedWindow = windowManager.getFocusedWindow(BoxHeroWindow); focusedWindow?.webviewContents?.navigationHistory.goBack(); }); // 앞으로 가기 ipcMain.handle("history/go-forward", () => { const focusedWindow = windowManager.getFocusedWindow(BoxHeroWindow); focusedWindow?.webviewContents?.navigationHistory.goForward(); }); // 새로고침 ipcMain.handle("history/reload", () => { const focusedWindow = windowManager.getFocusedWindow(BoxHeroWindow); focusedWindow?.webviewContents?.reload(); }); }; export default initNavigationIPC; ``` -------------------------------- ### Update .husky/pre-commit hook Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Update the pre-commit hook file to remove legacy shell initialization for Husky 9. ```bash # Before (husky 8) #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npx lint-staged # After (husky 9) - 더 간단해짐 npx lint-staged ``` -------------------------------- ### New ESLint Flat Configuration with eslint.config.js Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md This is the new configuration file for ESLint using the flat config system. It includes imports for necessary plugins and defines rules for TypeScript and import resolution. ```javascript import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; import importX from "eslint-plugin-import-x"; import globals from "globals"; export default tseslint.config( // 전역 무시 패턴 { ignores: [ "node_modules/**", "dist/**", "out/**", ".vite/**", "*.config.js", "*.config.ts", ], }, // 기본 설정 eslint.configs.recommended, ...tseslint.configs.recommended, // TypeScript 파일 설정 { files: ["**/*.ts", "**/*.tsx"], plugins: { "import-x": importX, }, languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, globals: { ...globals.browser, ...globals.node, ...globals.es2021, }, }, rules: { "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-unused-vars": [ "error", { argsIgnorePattern: "^_" }, ], "import-x/no-unresolved": ["error", { ignore: ["\.svg$"] }], }, settings: { "import-x/resolver": { typescript: true, node: true, }, }, } ); ``` -------------------------------- ### Manage Label Printer Process and IPC Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Handles the lifecycle of an external label printer binary and provides IPC handlers for printing tasks. Requires the binary to be present in the application resources. ```typescript // src/labelPrinter.ts import { app, BrowserWindow, ipcMain } from "electron"; import { spawn, execSync } from "child_process"; let labelPrinterProcess: ReturnType | null = null; // 라벨 프린터 바이너리 경로 조회 function getBinaryPath(): string { const platform = process.platform; const binaryName = platform === "win32" ? "label_printer.exe" : "label_printer"; if (app.isPackaged) { return path.join(process.resourcesPath, platform, binaryName); } return path.join(app.getAppPath(), "resources", "bin", platform, binaryName); } // 라벨 프린터 프로세스 시작 export function startLabelPrinter(): Promise { const binaryPath = getBinaryPath(); return new Promise((resolve, reject) => { const child = spawn(binaryPath, [], { env: { ...process.env, LABEL_PRINTER_PORT: "0" }, stdio: ["ignore", "pipe", "pipe"], }); labelPrinterProcess = child; child.stdout.on("data", (data: Buffer) => { const match = data.toString().match(/LABEL_PRINTER_PORT=(\d+)/); if (match) { resolve(parseInt(match[1], 10)); } }); child.on("error", reject); }); } // 라벨 프린터 프로세스 종료 export function stopLabelPrinter(): void { if (!labelPrinterProcess) return; if (process.platform === "win32") { execSync(`taskkill /pid ${labelPrinterProcess.pid} /T /F`); } else { labelPrinterProcess.kill("SIGTERM"); } labelPrinterProcess = null; } // 라벨 인쇄 IPC 핸들러 등록 export function registerLabelPrinterIPC(printerPort: number | null): void { ipcMain.handle("label-printer/print", async (_event, pdfBase64: string) => { if (!printerPort) throw new Error("라벨 프린터가 시작되지 않았습니다"); const printWin = new BrowserWindow({ width: 800, height: 600, show: false, webPreferences: { preload: path.join(__dirname, "printPreload.js"), }, }); printWin.loadURL(`http://127.0.0.1:${printerPort}/print`); printWin.webContents.on("did-finish-load", () => { printWin.webContents.send("pdf-data", pdfBase64); }); return new Promise((resolve) => { printWin.on("closed", resolve); }); }); } ``` -------------------------------- ### Migrate ESLint Configuration from .eslintrc.json to eslint.config.js Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md This snippet shows the old .eslintrc.json configuration which is no longer supported. It requires migrating to the new flat config format. ```json { "env": { "browser": true, "es6": true, "node": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:import/recommended", "plugin:import/electron", "plugin:import/typescript" ], "parser": "@typescript-eslint/parser", "rules": { "@typescript-eslint/ban-ts-comment": 0, "import/no-unresolved": ["error", { "ignore": [".svg"] }], "@typescript-eslint/no-unused-vars": [ "error", { "argsIgnorePattern": "^_" } ] } } ``` -------------------------------- ### Handle App IPC Requests Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Main process handlers for application-level features such as menus, external links, and locale settings. ```typescript // src/initialize/initAppIPC.ts import { app, ipcMain, shell } from "electron"; import i18n from "../locales/i18next"; import { getMainMenu } from "../menu"; const initAppIPC = () => { // 메인 메뉴 열기 ipcMain.handle("app/open-main-menu", () => { getMainMenu(i18n).popup({ x: 20, y: 38, }); }); // 언어 변경 ipcMain.handle("app/change-language", (_, lng: string) => { i18n.changeLanguage(lng); }); // 앱 로케일 조회 ipcMain.handle("app/get-app-locale", () => app.getLocale()); // 외부 링크 열기 ipcMain.handle("app/open-external-link", (_, url: string) => shell.openExternal(url) ); }; export default initAppIPC; ``` -------------------------------- ### Rollback ESLint Configuration Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to uninstall new ESLint packages and reinstall the previous versions, along with reverting the configuration file. ```bash npm uninstall eslint typescript-eslint eslint-plugin-import-x globals @eslint/js npm install -D eslint@8.57.1 @typescript-eslint/parser@6.21.0 @typescript-eslint/eslint-plugin@6.21.0 eslint-plugin-import@2.25.0 git checkout -- .eslintrc.json ``` -------------------------------- ### React Hook for Window State Subscription Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Subscribes to real-time window state changes (maximized, full screen) in the renderer process. Initializes by fetching the current state and updates via an event listener. ```typescript // src/renderers/main/hooks/useWindowStat.ts import { useEffect, useState } from "react"; const useWindowStat = () => { const [{ isMaximized, isFullScreen }, setWinStat] = useState({ isFullScreen: false, isMaximized: false, }); useEffect(() => { // 초기 상태 조회 window.electronAPI.window.getWindowStat().then(setWinStat); // 상태 변경 구독 const listener = (_: unknown, stat: { isFullScreen: boolean; isMaximized: boolean }) => { setWinStat(stat); }; window.electronAPI.window.onSyncWindowStat(listener); return () => { window.electronAPI.window.offSyncWindowStat(listener); }; }, []); return { isMaximized, isFullScreen }; }; export default useWindowStat; // 사용 예시 const TitleBar: React.FC = () => { const { isMaximized, isFullScreen } = useWindowStat(); return (
); }; ``` -------------------------------- ### Rollback React 19 Upgrade Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md Commands to revert to React and React DOM version 18.2.0 and reinstall the corresponding type packages. ```bash npm install react@18.2.0 react-dom@18.2.0 @types/react@18.2.29 @types/react-dom@18.2.14 ``` -------------------------------- ### Handle Window IPC Requests Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Main process handlers for window control operations like minimizing, maximizing, and closing. ```typescript // src/initialize/initWindowIPC.ts import { ipcMain } from "electron"; import { BoxHeroWindow, windowManager } from "../window"; const initWindowIPC = () => { // 윈도우 상태 조회 ipcMain.handle("window/get-stat", () => { const focusedWindow = windowManager.getFocusedWindow(BoxHeroWindow); if (!focusedWindow) return {}; return focusedWindow.windowStat; }); // 윈도우 최소화 ipcMain.handle("window/minimize", () => { const focusedWindow = windowManager.getFocusedWindow(); focusedWindow?.minimize(); }); // 윈도우 최대화/복원 토글 ipcMain.handle("window/maximize", () => { const focusedWindow = windowManager.getFocusedWindow(); if (!focusedWindow) return; if (focusedWindow.isMaximized()) { focusedWindow.unmaximize(); } else { focusedWindow.maximize(); } }); // 윈도우 닫기 ipcMain.handle("window/close", () => { const focusedWindow = windowManager.getFocusedWindow(); focusedWindow?.close(); }); // 최대화/전체화면/복원 토글 ipcMain.handle("window/toggle-maximize", () => { const focusedWindow = windowManager.getFocusedWindow(); if (!focusedWindow) return; if (focusedWindow.isFullScreen()) { focusedWindow.setFullScreen(false); } else if (focusedWindow.isMaximized()) { focusedWindow.unmaximize(); } else { focusedWindow.maximize(); } }); }; export default initWindowIPC; ``` -------------------------------- ### Register Linux Custom Protocol Source: https://github.com/bgpworks/boxhero-electron/blob/main/README.md Manually register the 'boxhero://' protocol for use in Linux development mode. ```sh # 등록 npm run setup:linux ``` ```sh # 해제 npm run setup:linux:unregister ``` -------------------------------- ### Update package.json for ESLint Migration Source: https://github.com/bgpworks/boxhero-electron/blob/main/docs/DEPENDENCY_UPGRADE_PLAN.md This JSON snippet shows the necessary modifications to package.json to enable module type and set up the lint script for the new ESLint configuration. ```json { "type": "module", "scripts": { "lint": "eslint ." } } ``` -------------------------------- ### Persist Window State Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Saves and restores window dimensions and position. Includes validation to ensure the window remains within visible screen bounds. ```typescript // src/windowState.ts import { app, screen } from "electron"; import fs from "fs"; import debounce from "lodash/debounce"; interface WindowState { position: { x: number; y: number }; size: { width: number; height: number }; isMaximized?: boolean; } // 윈도우 상태 조회 (화면 범위 검증 포함) export const getWindowState = (): WindowState => { const savedState = readWindowState(); // 윈도우가 화면 밖에 있으면 기본값 사용 if (!isWindowWithinBounds(savedState)) { return getDefaultState(); } return savedState; }; // 크기 저장 export const saveSize = (width: number, height: number) => { setWindowState("size", { width, height }); }; // 위치 저장 export const savePosition = (x: number, y: number) => { setWindowState("position", { x, y }); }; // 최대화 상태 저장 export const saveIsMaximized = (isMaximized: boolean) => { setWindowState("isMaximized", isMaximized); }; // 디바운스된 저장 함수 (리사이즈/이동 시 사용) export const saveSizeDebounced = debounce(saveSize, 300); export const savePositionDebounced = debounce(savePosition, 300); // 윈도우가 화면 범위 내에 있는지 확인 const isWindowWithinBounds = (state: WindowState): boolean => { const displays = screen.getAllDisplays(); return displays.some((display) => { const { x, y, width, height } = display.workArea; // 200x200 이상 보이는 디스플레이가 있으면 OK const overlapWidth = Math.max(0, Math.min(state.position.x + state.size.width, x + width) - Math.max(state.position.x, x) ); const overlapHeight = Math.max(0, Math.min(state.position.y + state.size.height, y + height) - Math.max(state.position.y, y) ); return overlapWidth >= 200 && overlapHeight >= 200; }); }; ``` -------------------------------- ### Handle BoxHero Custom Protocol for Desktop Authentication Source: https://context7.com/bgpworks/boxhero-electron/llms.txt Parses deep links using the boxhero:// custom protocol for desktop authentication. Includes CSRF protection by validating a state parameter. Ensure the custom protocol is registered with the operating system. ```typescript // src/initialize/initDesktopAuth.ts import { app, dialog, shell } from "electron"; import crypto from "crypto"; import { CUSTOM_PROTOCOL, AUTH_CALLBACK_PATH } from "../constants"; // CSRF 보호를 위한 state 저장 let pendingAuthState: string | null = null; // CSRF 보호를 위한 랜덤 state 생성 function generateAuthState(): string { return crypto.randomBytes(16).toString("hex"); } // boxhero://auth?code=xxx&state=xxx 형식의 딥링크 파싱 export function parseAuthDeepLink(url: string): { code: string; state: string | null } | null { try { const parsedUrl = new URL(url); if (parsedUrl.protocol !== `${CUSTOM_PROTOCOL}:`) return null; if (parsedUrl.hostname !== "auth") return null; const code = parsedUrl.searchParams.get("code"); if (!code) return null; return { code, state: parsedUrl.searchParams.get("state") }; } catch { return null; } } // state 검증 (timing-safe 비교) function validateAuthState(receivedState: string | null): boolean { if (receivedState === null || pendingAuthState === null) return false; if (pendingAuthState.length !== receivedState.length) return false; return crypto.timingSafeEqual( Buffer.from(pendingAuthState), Buffer.from(receivedState) ); } // 외부 브라우저로 인증 URL 열기 (state 파라미터 추가) export function openExternalForAuth(url: string): void { const state = generateAuthState(); pendingAuthState = state; const urlWithState = new URL(url); urlWithState.searchParams.set("state", state); shell.openExternal(urlWithState.toString()); } // 딥링크 처리 진입점 export function handleDeepLink(url: string): void { const authResult = parseAuthDeepLink(url); if (authResult) { if (!validateAuthState(authResult.state)) { dialog.showErrorBox("인증 오류", "인증 상태가 일치하지 않습니다."); pendingAuthState = null; return; } pendingAuthState = null; completeDesktopAuth(authResult.code); } } ``` -------------------------------- ### Define BoxHeroWindow Class Source: https://context7.com/bgpworks/boxhero-electron/llms.txt The main window class that hosts the BoxHero web application, handling window state and webview navigation. ```typescript // src/window.ts export class BoxHeroWindow extends ViteWindow { constructor() { const prevWindowState = getWindowState(); super("/templates/main.html", { ...prevWindowState.size, ...prevWindowState.position, show: false, minWidth: 1024, minHeight: 768, title: "BoxHero", webPreferences: { devTools: isDev, webviewTag: true, preload: path.join(__dirname, "preload.js"), }, backgroundColor: "#282c42", // Windows: 프레임 없음, macOS: 숨겨진 타이틀바 ...(isWindow ? { frame: false } : { titleBarStyle: "hiddenInset" }), }); } // 네비게이션 상태 조회 get navStat() { return { canGoBack: this.webviewContents?.navigationHistory.canGoBack() ?? false, canGoForward: this.webviewContents?.navigationHistory.canGoForward() ?? false, }; } // 윈도우 상태 조회 get windowStat() { return { isMaximized: this.isMaximized(), isFullScreen: this.isFullScreen(), }; } // webview 컨텐츠 접근 get webviewContents() { return webContents .getAllWebContents() .find( (wc) => wc.getType() === "webview" && wc.hostWebContents === this.webContents ); } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.