c9f5915419
This reverts commit 50c5039ec0.
182 lines
5.2 KiB
JavaScript
Executable File
182 lines
5.2 KiB
JavaScript
Executable File
const { app, BrowserWindow, shell, Menu, session } = require("electron");
|
|
const path = require("node:path");
|
|
const fs = require("node:fs");
|
|
|
|
// Gatekeeper / ad-hoc 서명 환경에서 GPU Helper SIGTRAP 완화
|
|
app.disableHardwareAcceleration();
|
|
app.commandLine.appendSwitch("disable-gpu");
|
|
app.commandLine.appendSwitch("disable-gpu-compositing");
|
|
app.commandLine.appendSwitch("disable-software-rasterizer");
|
|
app.commandLine.appendSwitch("no-sandbox");
|
|
app.commandLine.appendSwitch("disable-dev-shm-usage");
|
|
|
|
const WINDOW_WIDTH = 390;
|
|
const WINDOW_HEIGHT = 720;
|
|
|
|
const DEFAULT_URL =
|
|
process.env.CHATBOT_APP_URL ||
|
|
"https://chatbot.exdev.co.kr/chatbot/ai/?desktop=1";
|
|
|
|
const logFile = path.join(app.getPath("userData"), "ex-ai-chatbot.log");
|
|
|
|
function log(msg) {
|
|
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
try {
|
|
fs.appendFileSync(logFile, line);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
console.log(msg);
|
|
}
|
|
|
|
/** 챗봇 주소의 마이크·위치 요청은 매번 묻지 않고 허용한다. */
|
|
function allowMicrophoneForChatbot() {
|
|
const allowed = new Set([
|
|
"media",
|
|
"audioCapture",
|
|
"mediaKeySystem",
|
|
"geolocation",
|
|
]);
|
|
const appOrigin = (() => {
|
|
try {
|
|
return new URL(DEFAULT_URL).origin;
|
|
} catch {
|
|
return null;
|
|
}
|
|
})();
|
|
|
|
const isChatbotOrigin = (url) => {
|
|
if (!appOrigin || !url) return false;
|
|
try {
|
|
return new URL(url).origin === appOrigin;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const ses = session.defaultSession;
|
|
|
|
ses.setPermissionRequestHandler((webContents, permission, callback, details) => {
|
|
const url = details?.requestingUrl || webContents?.getURL();
|
|
callback(allowed.has(permission) && isChatbotOrigin(url));
|
|
});
|
|
|
|
ses.setPermissionCheckHandler((_webContents, permission, requestingOrigin, details) => {
|
|
const url = requestingOrigin || details?.requestingUrl;
|
|
return allowed.has(permission) && isChatbotOrigin(url);
|
|
});
|
|
|
|
if (typeof ses.setDevicePermissionHandler === "function") {
|
|
ses.setDevicePermissionHandler((details) => isChatbotOrigin(details?.origin));
|
|
}
|
|
}
|
|
|
|
function createWindow() {
|
|
const iconPath = path.join(__dirname, "build", process.platform === "win32" ? "icon.ico" : "icon.png");
|
|
const winOpts = {
|
|
width: WINDOW_WIDTH,
|
|
height: WINDOW_HEIGHT,
|
|
minWidth: 360,
|
|
minHeight: 520,
|
|
title: "한국도로공사 AI 챗봇",
|
|
backgroundColor: "#ffffff",
|
|
autoHideMenuBar: true,
|
|
show: false,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, "preload.js"),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: false,
|
|
webSecurity: true,
|
|
},
|
|
};
|
|
if (fs.existsSync(iconPath)) {
|
|
winOpts.icon = iconPath;
|
|
}
|
|
const win = new BrowserWindow(winOpts);
|
|
|
|
Menu.setApplicationMenu(null);
|
|
|
|
win.once("ready-to-show", () => {
|
|
win.show();
|
|
if (process.platform === "darwin" && app.dock) {
|
|
const dockIcon = path.join(__dirname, "build", "icon.png");
|
|
if (fs.existsSync(dockIcon)) {
|
|
try {
|
|
app.dock.setIcon(dockIcon);
|
|
} catch (e) {
|
|
log(`dock.setIcon failed: ${e}`);
|
|
}
|
|
}
|
|
app.dock.show();
|
|
}
|
|
});
|
|
|
|
win.webContents.on("did-fail-load", (_e, code, desc, url) => {
|
|
log(`did-fail-load code=${code} desc=${desc} url=${url}`);
|
|
// 네트워크 실패 시에도 빈 창으로 종료되지 않도록 안내 페이지
|
|
const html = `<!doctype html><meta charset="utf-8"/>
|
|
<body style="font:14px -apple-system,sans-serif;padding:40px;line-height:1.6">
|
|
<h2>연결 실패</h2>
|
|
<p>챗봇 서버에 연결하지 못했습니다.</p>
|
|
<p style="color:#666">${code}: ${desc}</p>
|
|
<p><a href="${DEFAULT_URL}">다시 시도</a></p>
|
|
</body>`;
|
|
win.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`);
|
|
});
|
|
|
|
win.webContents.on("render-process-gone", (_e, details) => {
|
|
log(`render-process-gone ${JSON.stringify(details)}`);
|
|
});
|
|
|
|
win.on("unresponsive", () => log("window unresponsive"));
|
|
|
|
log(`loadURL ${DEFAULT_URL}`);
|
|
win.loadURL(DEFAULT_URL).catch((err) => log(`loadURL error: ${err}`));
|
|
|
|
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
shell.openExternal(url);
|
|
return { action: "deny" };
|
|
});
|
|
|
|
return win;
|
|
}
|
|
|
|
const gotLock = app.requestSingleInstanceLock();
|
|
if (!gotLock) {
|
|
app.quit();
|
|
} else {
|
|
app.on("second-instance", () => {
|
|
const wins = BrowserWindow.getAllWindows();
|
|
if (wins.length > 0) {
|
|
if (wins[0].isMinimized()) wins[0].restore();
|
|
wins[0].focus();
|
|
} else {
|
|
createWindow();
|
|
}
|
|
});
|
|
|
|
app.whenReady().then(() => {
|
|
app.setName("EX AI Chatbot");
|
|
log(`ready userData=${app.getPath("userData")}`);
|
|
allowMicrophoneForChatbot();
|
|
createWindow();
|
|
|
|
app.on("activate", () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
createWindow();
|
|
}
|
|
});
|
|
});
|
|
|
|
app.on("window-all-closed", () => {
|
|
if (process.platform !== "darwin") {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
process.on("uncaughtException", (err) => {
|
|
log(`uncaughtException ${err?.stack || err}`);
|
|
});
|
|
}
|