Files
dify_market_manager_gui/src/main/window.js
2025-07-23 18:16:47 +08:00

703 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { BrowserWindow, screen, Menu, shell, session, app, globalShortcut, dialog ,systemPreferences } from 'electron'
import { join } from 'path'
import { is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import { isApiDatasetsDocumentsPath, extractIdFromPath } from './utils/difyUtils.js'
import XEUtils from 'xe-utils'
import logger from './utils/logger'
import { checkDefaultClientConnection } from './utils/networkUtils.js'
import { createMenu } from './menu.js'
import { setStoreValue, getStoreValue,deleteStore } from './store.js'
import {checkForUpdates} from "./utils/updateUtils"
import dayjs from 'dayjs'
let mainWindow = null
let difyfullScreenWindow = null
let drageWindow = null
let apiConfigWindow = null
let configWindow = null
import { clearAllSessionData } from './utils/cacheUtils.js'
// 权限授权
async function checkMediaAccess(mediaType){
const result = systemPreferences.getMediaAccessStatus(mediaType)
logger.info(`=====================systemPreferences.getMediaAccessStatus:${mediaType} result:${result}`)
if(result !== "granted"){
await systemPreferences.askForMediaAccess(mediaType)
}
}
async function checkAndApplyDeviceAccessPrivilege() {
if (process.platform === "darwin" || process.platform === 'win32') {
// 检查并申请摄像头权限
const cameraPrivilege = systemPreferences.getMediaAccessStatus("camera");
if (cameraPrivilege !== "granted") {
await systemPreferences.askForMediaAccess("camera");
}
// 检查并申请麦克风权限
const micPrivilege = systemPreferences.getMediaAccessStatus("microphone");
if (micPrivilege !== "granted") {
await systemPreferences.askForMediaAccess("microphone");
}
// 检查访问屏幕权限
const screenPrivilege = systemPreferences.getMediaAccessStatus("screen");
if (screenPrivilege !== 'granted') {
// 没有屏幕访问权限,做后续处理...
}
}
}
export async function createWindow() {
// 如果窗口已经存在,直接显示并返回
if (mainWindow) {
mainWindow.show()
return
}
logger.info(`启动主窗口`)
Menu.setApplicationMenu(null)
// Create the browser window.
mainWindow = new BrowserWindow({
width: 420,
height: 900,
show: false,
media: {
audio: true,
video: true
},
icon: icon,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
preload: join(__dirname, '../preload/index.js'),
// sandbox: false
}
})
// 创建菜单
createMenu(mainWindow, difyfullScreenWindow)
let code = `localStorage.setItem("IsHsAiApp","IsHsAiApp");localStorage.setItem("HsAppCode",${import.meta.env.VITE_HsAppCode});`
const isAdmin = getStoreValue("isAdmin")
let display='';
await checkAndApplyDeviceAccessPrivilege()
mainWindow.webContents.on('did-finish-load', () => {
mainWindow.webContents
.executeJavaScript(code)
.then((result) => {
})
.catch((error) => {
console.error('Error:', error)
})
})
// 监听快捷键 F12 来打开/关闭 DevTools
mainWindow.webContents.on('before-input-event', (event, input) => {
if (input.key === 'F12') {
mainWindow.webContents.toggleDevTools()
} else if (input.key === 'F5') {
logger.info('主窗口 F5 快捷键触发')
mainWindow.reload()
}
})
mainWindow.on('resize',async () => {
// logger.info('窗口大小发生变化...');
// 触发窗口内容重新布局,而不是重新加载
mainWindow.webContents.executeJavaScript(`
if (document.body) {
// 触发窗口重排
window.dispatchEvent(new Event('resize'));
}
`).catch(err => {
console.error('执行布局调整时出错:', err);
});
});
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault()
mainWindow.hide()
return false
}
})
// setStoreValue("h5_client_url", "")
// 检查本地存储中是否已有 h5_client_url
const existingH5ClientUrl = getStoreValue("h5_client_url")
if (!existingH5ClientUrl || existingH5ClientUrl.trim() === '') {
// 只有当值不存在或为空时,才设置默认值
// setStoreValue("h5_client_url", "http://work.ii999.live:20040")
// setStoreValue("h5_client_url", "http://10.102.8.56:18900")
// setStoreValue("h5_client_url", "http://68.66.24.160:18900")
setStoreValue("h5_client_url", import.meta.env.VITE_h5_client_url)
}
const h5_client_url=getStoreValue("h5_client_url")+"/h5_client/"
logger.info("==================================== mainWindow.loadURL:"+h5_client_url)
// 加载存储的 URL
mainWindow.loadURL(h5_client_url)
// 监听页面加载失败事件
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
logger.error(`主窗口页面加载失败: code=${errorCode}, desc=${errorDescription}, url=${validatedURL}`)
// 跳转到网络错误页面
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/#/network_error')
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'), { hash: '/network_error' })
}
})
// 接口超过30分钟不活动则退出登录
await tokenExpireTimer()
setTimeout(async ()=>{
try {
// 注册全局快捷键
registerShortcuts(mainWindow)
// 先检测网络连接,如果网络正常再调用版本检测升级
const isNetworkConnected = await checkDefaultClientConnection(5000);
if (isNetworkConnected) {
logger.info('网络连接正常,开始检查版本更新');
checkForUpdates({},false)
} else {
logger.warn('网络连接异常,跳过版本检测升级');
}
}catch (e) {
logger.info(e)
}
},1500)
session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
if (permission === 'microphone') {
// 用户是否允许使用麦克风
callback(true); // 或者根据实际情况返回false
} else {
// 对于其他权限,拒绝或根据实际情况处理
callback(false);
}
});
}
// 添加一个专门的快捷键注册函数
function registerShortcuts(window=null) {
// 先注销可能存在的F5快捷键
globalShortcut.unregister('F5')
// 注册 F5 刷新快捷键
const success = globalShortcut.register('F5', () => {
logger.info('F5 快捷键触发')
try {
// 获取当前焦点窗口
const focusedWindow = BrowserWindow.getFocusedWindow()
logger.info('当前焦点窗口:', focusedWindow ? '存在' : '不存在')
if (focusedWindow && !focusedWindow.isDestroyed()) {
logger.info('刷新当前焦点窗口')
focusedWindow.reload()
} else if (mainWindow && !mainWindow.isDestroyed()) {
logger.info('没有焦点窗口,刷新主窗口')
mainWindow.reload()
} else if (difyfullScreenWindow && !difyfullScreenWindow.isDestroyed()) {
logger.info('主窗口不可用,刷新全屏窗口')
difyfullScreenWindow.reload()
} else {
logger.warn('没有可用的窗口进行刷新')
}
} catch (error) {
logger.error('F5快捷键执行出错:', error)
}
})
logger.info(`F5快捷键注册${success ? '成功' : '失败'}`)
const isRegistered_F12 = globalShortcut.isRegistered('F12');
logger.info(`Is F12 registered: ${isRegistered_F12}`);
// 桌面端快要退出的时候,注销快捷键
app.on('will-quit', () => {
globalShortcut.unregisterAll() // 注销所有快捷键
})
}
export async function createNewWindow(url, access_token, refresh_token,sandbox=false) {
const origin_url = url
logger.info('==========createNewWindow')
logger.info('==========createNewWindow sandbox',sandbox)
await checkAndApplyDeviceAccessPrivilege()
difyfullScreenWindow = new BrowserWindow({
width: 1920,
height: 1200,
show: false,
icon: icon,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
preload: join(__dirname, '../preload/index.js'),
sandbox: sandbox,
webSecurity: false, // 注意:在生产环境中应谨慎使用
enableRemoteModule: true, // Electron 10+ 默认禁用,需要显式启用
}
})
difyfullScreenWindow.on('ready-to-show', () => {
difyfullScreenWindow.show()
// 确保快捷键在新窗口显示后也能工作
registerShortcuts(difyfullScreenWindow)
})
let code = `localStorage.setItem("IsHsAiApp","IsHsAiApp");localStorage.setItem("console_token","${access_token}");localStorage.setItem("refresh_token","12");`
const isAdmin = getStoreValue("isAdmin")
const hs_version = getStoreValue("hs_version")
let display='';
if (isAdmin==true){
display = `
(function() {
const stickyDivs = document.querySelectorAll('.sticky.top-0.left-0.right-0.basis-auto.shrink-0');
stickyDivs.forEach(div => {
// div.style.display = 'none';
});
})();
`
}else {
display = `
(function() {
const stickyDivs = document.querySelectorAll('.sticky.top-0.left-0.right-0.basis-auto.shrink-0');
stickyDivs.forEach(div => {
div.style.display = 'none';
});
})();
`
}
code=code+`document.cookie="hs_version=${hs_version}";`
difyfullScreenWindow.webContents.on('did-finish-load', () => {
difyfullScreenWindow.webContents
.executeJavaScript(code + display)
.then((result) => {
})
.catch((error) => {
console.error('Error:', error)
})
let times = 0
let timer = setInterval(() => {
if (times > 3) {
clearInterval(timer)
timer = null
times = 0
return
}
if (difyfullScreenWindow != null) {
difyfullScreenWindow.webContents
.executeJavaScript(code + display)
.then((result) => {
times = times + 1
})
.catch((error) => {
console.error('Error:', error)
})
}
}, 2000)
})
// 监听快捷键 F12 来打开/关闭 DevTools
difyfullScreenWindow.webContents.on('before-input-event', (event, input) => {
if (input.key === 'F12') {
difyfullScreenWindow.webContents.toggleDevTools()
} else if (input.key === 'F5') {
logger.info('全屏窗口 F5 快捷键触发')
difyfullScreenWindow.reload()
}
})
difyfullScreenWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
difyfullScreenWindow.on('closed', () => {
logger.info('主窗口已关闭')
difyfullScreenWindow = null
})
difyfullScreenWindow.webContents.on('did-navigate', (event, url) => {
logger.info('navigate to new URL:', url)
})
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Access-Control-Allow-Origin': ['*'],
'Access-Control-Allow-Methods': ['GET, POST, PUT, DELETE, OPTIONS'],
'Access-Control-Allow-Headers': ['Content-Type, Authorization']
}
})
})
let times = 0
difyfullScreenWindow.webContents.on('did-navigate-in-page', (event, url) => {
logger.info('navigate URL change:', url)
logger.info(`===============current:${access_token}`)
if (url.includes('/signin')) {
logger.info("token 注入失效,重新注入并且加载页面")
if (times>3){
difyfullScreenWindow.close()
difyfullScreenWindow=null
times=0;
return
}
let _display = `
(function() {
const stickyDivs = document.querySelectorAll('body');
stickyDivs.forEach(div => {
div.style.display = 'none';
});
})();
`
difyfullScreenWindow.webContents
.executeJavaScript(code + _display)
.then((result) => {
logger.info('load url:', origin_url)
setTimeout(()=>{
times=times+1;
difyfullScreenWindow.loadURL(origin_url)
},1000)
})
.catch((error) => {
console.error('Error:', error)
})
}
})
let dify_site = 'http://df.1024web.cn'
const filter = { urls: [`${dify_site}/*`] }
session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => {
if (isApiDatasetsDocumentsPath(details.url)) {
logger.info('========== onBeforeRequest url:', details.url)
logger.info('============datasetId :', extractIdFromPath(details.url))
logger.info(details)
const url = new URL(details.url)
const pathAndQuery = url.pathname + url.search
const redirectURL = `http://192.168.50.22:8001/items/`
logger.info(`[basehttp:setProxy] redirectURL = ${redirectURL}`)
callback({ redirectURL })
} else {
callback({ cancel: false })
}
})
difyfullScreenWindow.on('resize', () => {
logger.info('窗口大小发生变化,重新加载页面...');
if (difyfullScreenWindow!=null){
difyfullScreenWindow.webContents.executeJavaScript(`
if (document.body) {
// 触发窗口重排
window.dispatchEvent(new Event('resize'));
}
`).catch(err => {
console.error('执行布局调整时出错:', err);
});
}
// 触发窗口内容重新布局,而不是重新加载
});
difyfullScreenWindow.loadURL(url)
}
export function createDrageWindow() {
logger.info('开始创建悬浮窗口')
if (drageWindow) {
drageWindow.focus()
return
}
drageWindow = new BrowserWindow({
width: 120,
height: 120,
frame: false,
show: true,
skipTaskbar: true,
transparent: true,
resizable: false,
alwaysOnTop: true,
autoHideMenuBar: true,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
preload: join(__dirname, '../preload/index.js')
}
})
drageWindow.setAlwaysOnTop(true, 'screen-saver')
drageWindow.on('ready-to-show', () => {
drageWindow.show()
})
drageWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
drageWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault()
drageWindow.hide()
return false
}
})
const h5_client_url=getStoreValue("h5_client_url")+"/electron_h5/"
logger.log("======================== drageWindow :")
logger.log(h5_client_url)
drageWindow.loadURL(h5_client_url)
const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize
drageWindow.setPosition(screenWidth - drageWindow.getSize()[0] -100, screenHeight - drageWindow.getSize()[1] - 100)
}
export async function tokenExpireTimer(){
let lastLogTime = 0; // 记录上次打印日志的时间
const LOG_INTERVAL = 60000; // 日志打印间隔60秒打印一次
const tokenExpireTimer = setInterval(async () => {
const currentTime = Date.now();
const lastActiveTime = getStoreValue("lastActiveTime")||null
if (lastActiveTime!=null){
try {
const nowTime=dayjs();
const lastTime=dayjs(lastActiveTime);
const diff=nowTime.diff(lastTime, 'minute')
// 只在特定条件下打印日志,减少日志频率
if (currentTime - lastLogTime > LOG_INTERVAL) {
logger.info(`tokenExpireTimer 检查 - 时间差距: ${diff}分钟`)
lastLogTime = currentTime;
}
if ( diff> 30) {
logger.info(`用户超时退出登录 - 时间差距: ${diff}分钟`)
deleteStore("lastActiveTime")
try {
// 清除所有窗口的浏览器缓存
const windows = BrowserWindow.getAllWindows()
for (const window of windows) {
const session = window.webContents.session
await session.clearStorageData({
storages: [
'appcache',
'cookies',
'filesystem',
'indexdb',
'localstorage',
'shadercache',
'websql',
'serviceworkers',
'cachestorage'
]
})
}
logger.info('浏览器缓存清除成功')
} catch (error) {
logger.error('清除浏览器缓存失败:', error)
}
if (mainWindow) {
mainWindow.reload()
}
logger.info('用户已退出登录')
}else {
logger.info(`用户正常登录 - 时间差距: ${diff}分钟`)
}
} catch (e) {
logger.error('tokenExpireTimer 执行错误:', e)
}
}
}, 1000 * 10)
}
export function getMainWindow() {
if (!mainWindow || mainWindow.isDestroyed()) {
return null
}
return mainWindow
}
export function getDragWindow() {
if (!drageWindow || drageWindow.isDestroyed()) {
return null
}
return drageWindow
}
export function getDifyFullScreenWindow() {
return difyfullScreenWindow;
}
export function getDrageWindow() {
return drageWindow;
}
// 在应用退出时注销所有快捷键
export function unregisterAllShortcuts() {
globalShortcut.unregisterAll()
}
export function closeApiConfigWindow() {
if (apiConfigWindow) {
apiConfigWindow.close()
apiConfigWindow = null
}
}
export function createConfigWindow() {
if (configWindow) {
configWindow.focus()
return
}
configWindow = new BrowserWindow({
width: 600,
height: 400,
resizable: false,
minimizable: false,
maximizable: false,
parent: getMainWindow(),
modal: true,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
preload: join(__dirname, '../preload/index.js')
},
permissions: {
microphone: 'allow'
}
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
configWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/#/config')
} else {
configWindow.loadFile(join(__dirname, '../renderer/index.html'), { hash: '/config' })
}
//
configWindow.on('closed', () => {
configWindow = null
})
}
// 添加关闭配置窗口的方法
export function closeConfigWindow() {
if (configWindow) {
configWindow.close()
configWindow = null
}
}
// 测试快捷键是否正常工作
export function testShortcuts() {
logger.info('测试快捷键功能...')
// 检查F5快捷键是否已注册
const isF5Registered = globalShortcut.isRegistered('F5')
logger.info(`F5快捷键是否已注册: ${isF5Registered}`)
// 获取所有已注册的快捷键
const allShortcuts = globalShortcut.isRegistered('F5') ? ['F5'] : []
logger.info(`已注册的快捷键: ${allShortcuts.join(', ')}`)
// 获取当前焦点窗口
const focusedWindow = BrowserWindow.getFocusedWindow()
logger.info(`当前焦点窗口: ${focusedWindow ? '存在' : '不存在'}`)
if (focusedWindow) {
logger.info(`焦点窗口标题: ${focusedWindow.getTitle()}`)
logger.info(`焦点窗口是否销毁: ${focusedWindow.isDestroyed()}`)
}
return {
f5Registered: isF5Registered,
focusedWindow: focusedWindow ? 'exists' : 'none',
mainWindow: mainWindow && !mainWindow.isDestroyed() ? 'exists' : 'none',
difyWindow: difyfullScreenWindow && !difyfullScreenWindow.isDestroyed() ? 'exists' : 'none'
}
}