🏗️ refactor data access layer and enhance service architecture

This commit is contained in:
刘嘉伟 2025-02-25 11:57:54 +08:00
parent ff8b01c360
commit 9946b50d5d
30 changed files with 2255 additions and 2199 deletions

View File

@ -1,337 +1,337 @@
import { app, dialog, ipcMain, shell } from "electron";
import { clearConfig, getConfig, saveConfig } from "../storage/config";
import { clearVersion, listVersion } from "../storage/version";
import { genIniConfig, genTomlConfig, stopFrpcProcess } from "./frpc";
import { clearProxy, insertProxy, listProxy } from "../storage/proxy";
import path from "path";
import fs from "fs";
import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log";
const toml = require("@iarna/toml");
const { v4: uuidv4 } = require("uuid");
export const initConfigApi = win => {
ipcMain.on("config.saveConfig", async (event, args) => {
logInfo(LogModule.APP, "Attempting to save configuration.");
saveConfig(args, (err, numberOfUpdated, upsert) => {
if (!err) {
const start = args.systemSelfStart || false;
logDebug(LogModule.APP, "Startup status set to: " + start);
app.setLoginItemSettings({
openAtLogin: start, //win
openAsHidden: start //macOs
});
logInfo(LogModule.APP, "Configuration saved successfully.");
} else {
logError(LogModule.APP, `Error saving configuration: ${err}`);
}
event.reply("Config.saveConfig.hook", {
err: err,
numberOfUpdated: numberOfUpdated,
upsert: upsert
});
});
});
ipcMain.on("config.getConfig", async (event, args) => {
logInfo(LogModule.APP, "Requesting configuration.");
getConfig((err, doc) => {
if (err) {
logError(LogModule.APP, `Error retrieving configuration: ${err}`);
}
event.reply("Config.getConfig.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.versions", event => {
logInfo(LogModule.APP, "Requesting version information.");
listVersion((err, doc) => {
if (err) {
logError(LogModule.APP, `Error retrieving version information: ${err}`);
}
event.reply("Config.versions.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.hasConfig", event => {
logInfo(LogModule.APP, "Checking if configuration exists.");
getConfig((err, doc) => {
if (err) {
logError(LogModule.APP, `Error checking configuration: ${err}`);
}
event.reply("Config.getConfig.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.exportConfig", async (event, args) => {
logInfo(LogModule.APP, "Attempting to export configuration.");
const result = await dialog.showOpenDialog({
properties: ["openDirectory"]
});
const outputDirectory = result.filePaths[0];
if (!outputDirectory) {
logWarn(LogModule.APP, "Export canceled by user.");
return;
}
logInfo(
LogModule.APP,
`Exporting configuration to directory ${outputDirectory} with type: ${args}`
);
getConfig((err1, config) => {
if (!err1 && config) {
listProxy((err2, proxys) => {
if (!err2) {
let configContent = "";
if (args === "ini") {
configContent = genIniConfig(config, proxys);
} else if (args === "toml") {
configContent = genTomlConfig(config, proxys);
}
const configPath = path.join(
outputDirectory,
`frpc-desktop.${args}`
);
fs.writeFile(
configPath, // 配置文件目录
configContent, // 配置文件内容
{ flag: "w" },
err => {
if (err) {
logError(
LogModule.APP,
`Error writing configuration file: ${err}`
);
event.reply("config.exportConfig.hook", {
data: "导出错误",
err: err
});
} else {
logInfo(
LogModule.APP,
"Configuration exported successfully."
);
event.reply("Config.exportConfig.hook", {
data: {
configPath: configPath
}
});
}
}
);
} else {
logError(LogModule.APP, `Error listing proxies: ${err2}`);
}
});
} else {
logError(LogModule.APP, `Error retrieving configuration: ${err1}`);
}
});
});
const parseTomlConfig = (tomlPath: string) => {
logInfo(LogModule.APP, `Parsing TOML configuration from ${tomlPath}`);
const importConfigPath = tomlPath;
const tomlData = fs.readFileSync(importConfigPath, "utf-8");
logInfo(LogModule.APP, "Configuration content read successfully.");
const sourceConfig = toml.parse(tomlData);
// 解析config
const targetConfig: FrpConfig = {
currentVersion: null,
serverAddr: sourceConfig.serverAddr || "",
serverPort: sourceConfig.serverPort || "",
authMethod: sourceConfig?.user
? "multiuser"
: sourceConfig?.auth?.method || "",
authToken: sourceConfig?.auth?.token || "",
transportHeartbeatInterval:
sourceConfig?.transport?.heartbeatInterval || 30,
transportHeartbeatTimeout:
sourceConfig?.transport?.heartbeatTimeout || 90,
tlsConfigEnable: sourceConfig?.transport?.tls?.enable || false,
tlsConfigCertFile: sourceConfig?.transport?.tls?.certFile || "",
tlsConfigKeyFile: sourceConfig?.transport?.tls?.keyFile || "",
tlsConfigServerName: sourceConfig?.transport?.tls?.serverName || "",
tlsConfigTrustedCaFile: sourceConfig?.transport?.tls?.trustedCaFile || "",
logLevel: sourceConfig?.log?.level || "info",
logMaxDays: sourceConfig?.log?.maxDays || 3,
proxyConfigProxyUrl: sourceConfig?.transport?.proxyURL || "",
proxyConfigEnable: Boolean(sourceConfig?.transport?.proxyURL) || false,
user: sourceConfig?.user || "",
metaToken: sourceConfig?.metadatas?.token || "",
systemSelfStart: false,
systemStartupConnect: false,
systemSilentStartup: false,
webEnable: true,
webPort: sourceConfig?.webServer?.port || 57400,
transportProtocol: sourceConfig?.transport?.protocol || "tcp",
transportDialServerTimeout:
sourceConfig?.transport?.dialServerTimeout || 10,
transportDialServerKeepalive:
sourceConfig?.transport?.dialServerKeepalive || 70,
transportPoolCount: sourceConfig?.transport?.poolCount || 0,
transportTcpMux: sourceConfig?.transport?.tcpMux || true,
transportTcpMuxKeepaliveInterval:
sourceConfig?.transport?.tcpMuxKeepaliveInterval || 7200
};
let frpcProxys = [];
// 解析proxy
if (sourceConfig?.proxies && sourceConfig.proxies.length > 0) {
const frpcProxys1 = sourceConfig.proxies.map(m => {
const rm: Proxy = {
_id: uuidv4(),
name: m?.name,
type: m?.type,
localIp: m?.localIP || "",
localPort: m?.localPort || null,
remotePort: m?.remotePort || null,
customDomains: m?.customDomains || [],
subdomain: m.subdomain || "",
basicAuth: m.basicAuth || false,
httpUser: m.httpUser || "",
httpPassword: m.httpPassword || "",
// 以下为stcp参数
stcpModel: "visited",
serverName: "",
secretKey: m?.secretKey || "",
bindAddr: "",
bindPort: null,
status: m?.status || true,
fallbackTo: m?.fallbackTo,
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500,
keepTunnelOpen: m?.keepTunnelOpen || false,
https2http: m?.https2http || false,
https2httpCaFile: m?.https2httpCaFile || "",
https2httpKeyFile: m?.https2httpKeyFile || ""
};
return rm;
});
frpcProxys = [...frpcProxys, ...frpcProxys1];
logInfo(LogModule.APP, "Parsed proxies from configuration.");
}
// 解析stcp的访问者
if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) {
const frpcProxys2 = sourceConfig.visitors.map(m => {
const rm: Proxy = {
_id: uuidv4(),
name: m?.name,
type: m?.type,
localIp: "",
localPort: null,
remotePort: null,
customDomains: [],
subdomain: m.subdomain || "",
basicAuth: m.basicAuth || false,
httpUser: m.httpUser || "",
httpPassword: m.httpPassword || "",
// 以下为stcp参数
stcpModel: "visitors",
serverName: m?.serverName,
secretKey: m?.secretKey || "",
bindAddr: m?.bindAddr,
bindPort: m?.bindPort,
status: m?.status || true,
fallbackTo: m?.fallbackTo,
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500,
keepTunnelOpen: m?.keepTunnelOpen || false,
https2http: m?.https2http || false,
https2httpCaFile: m?.https2httpCaFile || "",
https2httpKeyFile: m?.https2httpKeyFile || ""
};
return rm;
});
frpcProxys = [...frpcProxys, ...frpcProxys2];
logInfo(LogModule.APP, "Parsed visitors from configuration.");
}
if (targetConfig) {
clearConfig(() => {
logInfo(LogModule.APP, "Clearing existing configuration.");
saveConfig(targetConfig);
logInfo(LogModule.APP, "New configuration saved.");
});
}
if (frpcProxys && frpcProxys.length > 0) {
clearProxy(() => {
frpcProxys.forEach(f => {
insertProxy(f, err => {
if (err) {
logError(LogModule.APP, `Error inserting proxy: ${err}`);
} else {
logInfo(LogModule.APP, `Inserted proxy: ${JSON.stringify(f)}`);
}
});
});
});
}
};
ipcMain.on("config.importConfig", async (event, args) => {
logInfo(LogModule.APP, "Attempting to import configuration.");
const result = await dialog.showOpenDialog(win, {
properties: ["openFile"],
filters: [
{ name: "FrpcConfig Files", extensions: ["toml", "ini"] } // 允许选择的文件类型
]
});
if (result.canceled) {
logWarn(LogModule.APP, "Import canceled by user.");
return;
} else {
const filePath = result.filePaths[0];
const fileExtension = path.extname(filePath); // 获取文件后缀名
logWarn(
LogModule.APP,
`Importing file ${filePath} with extension ${fileExtension}`
);
if (fileExtension === ".toml") {
parseTomlConfig(filePath);
event.reply("Config.importConfig.hook", {
success: true
});
} else {
logError(
LogModule.APP,
`Import failed, unsupported file format: ${fileExtension}`
);
event.reply("Config.importConfig.hook", {
success: false,
data: `导入失败,暂不支持 ${fileExtension} 格式文件`
});
}
}
});
ipcMain.on("config.clearAll", async (event, args) => {
logInfo(LogModule.APP, "Clearing all configurations.");
stopFrpcProcess(() => {
clearConfig();
clearProxy();
clearVersion();
event.reply("Config.clearAll.hook", {});
logInfo(LogModule.APP, "All configurations cleared.");
});
});
ipcMain.on("config.openDataFolder", async (event, args) => {
const userDataPath = app.getPath("userData");
logInfo(LogModule.APP, "Attempting to open data folder.");
shell.openPath(userDataPath).then(errorMessage => {
if (errorMessage) {
logError(LogModule.APP, `Failed to open data folder: ${errorMessage}`);
event.reply("Config.openDataFolder.hook", false);
} else {
logInfo(LogModule.APP, "Data folder opened successfully.");
event.reply("Config.openDataFolder.hook", true);
}
});
});
};
// import { app, dialog, ipcMain, shell } from "electron";
// import { clearConfig, getConfig, saveConfig } from "../storage/config";
// import { clearVersion, listVersion } from "../storage/version";
// import { genIniConfig, genTomlConfig, stopFrpcProcess } from "./frpc";
// import { clearProxy, insertProxy, listProxy } from "../storage/proxy";
// import path from "path";
// import fs from "fs";
// import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log";
//
// const toml = require("@iarna/toml");
// const { v4: uuidv4 } = require("uuid");
//
// export const initConfigApi = win => {
// ipcMain.on("config.saveConfig", async (event, args) => {
// logInfo(LogModule.APP, "Attempting to save configuration.");
// saveConfig(args, (err, numberOfUpdated, upsert) => {
// if (!err) {
// const start = args.systemSelfStart || false;
// logDebug(LogModule.APP, "Startup status set to: " + start);
// app.setLoginItemSettings({
// openAtLogin: start, //win
// openAsHidden: start //macOs
// });
// logInfo(LogModule.APP, "Configuration saved successfully.");
// } else {
// logError(LogModule.APP, `Error saving configuration: ${err}`);
// }
// event.reply("Config.saveConfig.hook", {
// err: err,
// numberOfUpdated: numberOfUpdated,
// upsert: upsert
// });
// });
// });
//
// ipcMain.on("config.getConfig", async (event, args) => {
// logInfo(LogModule.APP, "Requesting configuration.");
// getConfig((err, doc) => {
// if (err) {
// logError(LogModule.APP, `Error retrieving configuration: ${err}`);
// }
// event.reply("Config.getConfig.hook", {
// err: err,
// data: doc
// });
// });
// });
//
// ipcMain.on("config.versions", event => {
// logInfo(LogModule.APP, "Requesting version information.");
// listVersion((err, doc) => {
// if (err) {
// logError(LogModule.APP, `Error retrieving version information: ${err}`);
// }
// event.reply("Config.versions.hook", {
// err: err,
// data: doc
// });
// });
// });
//
// ipcMain.on("config.hasConfig", event => {
// logInfo(LogModule.APP, "Checking if configuration exists.");
// getConfig((err, doc) => {
// if (err) {
// logError(LogModule.APP, `Error checking configuration: ${err}`);
// }
// event.reply("Config.getConfig.hook", {
// err: err,
// data: doc
// });
// });
// });
//
// ipcMain.on("config.exportConfig", async (event, args) => {
// logInfo(LogModule.APP, "Attempting to export configuration.");
// const result = await dialog.showOpenDialog({
// properties: ["openDirectory"]
// });
// const outputDirectory = result.filePaths[0];
// if (!outputDirectory) {
// logWarn(LogModule.APP, "Export canceled by user.");
// return;
// }
//
// logInfo(
// LogModule.APP,
// `Exporting configuration to directory ${outputDirectory} with type: ${args}`
// );
// getConfig((err1, config) => {
// if (!err1 && config) {
// listProxy((err2, proxys) => {
// if (!err2) {
// let configContent = "";
// if (args === "ini") {
// configContent = genIniConfig(config, proxys);
// } else if (args === "toml") {
// configContent = genTomlConfig(config, proxys);
// }
// const configPath = path.join(
// outputDirectory,
// `frpc-desktop.${args}`
// );
// fs.writeFile(
// configPath, // 配置文件目录
// configContent, // 配置文件内容
// { flag: "w" },
// err => {
// if (err) {
// logError(
// LogModule.APP,
// `Error writing configuration file: ${err}`
// );
// event.reply("config.exportConfig.hook", {
// data: "导出错误",
// err: err
// });
// } else {
// logInfo(
// LogModule.APP,
// "Configuration exported successfully."
// );
// event.reply("Config.exportConfig.hook", {
// data: {
// configPath: configPath
// }
// });
// }
// }
// );
// } else {
// logError(LogModule.APP, `Error listing proxies: ${err2}`);
// }
// });
// } else {
// logError(LogModule.APP, `Error retrieving configuration: ${err1}`);
// }
// });
// });
//
// const parseTomlConfig = (tomlPath: string) => {
// logInfo(LogModule.APP, `Parsing TOML configuration from ${tomlPath}`);
// const importConfigPath = tomlPath;
// const tomlData = fs.readFileSync(importConfigPath, "utf-8");
// logInfo(LogModule.APP, "Configuration content read successfully.");
// const sourceConfig = toml.parse(tomlData);
// // 解析config
// const targetConfig: FrpConfig = {
// currentVersion: null,
// serverAddr: sourceConfig.serverAddr || "",
// serverPort: sourceConfig.serverPort || "",
// authMethod: sourceConfig?.user
// ? "multiuser"
// : sourceConfig?.auth?.method || "",
// authToken: sourceConfig?.auth?.token || "",
// transportHeartbeatInterval:
// sourceConfig?.transport?.heartbeatInterval || 30,
// transportHeartbeatTimeout:
// sourceConfig?.transport?.heartbeatTimeout || 90,
// tlsConfigEnable: sourceConfig?.transport?.tls?.enable || false,
// tlsConfigCertFile: sourceConfig?.transport?.tls?.certFile || "",
// tlsConfigKeyFile: sourceConfig?.transport?.tls?.keyFile || "",
// tlsConfigServerName: sourceConfig?.transport?.tls?.serverName || "",
// tlsConfigTrustedCaFile: sourceConfig?.transport?.tls?.trustedCaFile || "",
// logLevel: sourceConfig?.log?.level || "info",
// logMaxDays: sourceConfig?.log?.maxDays || 3,
// proxyConfigProxyUrl: sourceConfig?.transport?.proxyURL || "",
// proxyConfigEnable: Boolean(sourceConfig?.transport?.proxyURL) || false,
// user: sourceConfig?.user || "",
// metaToken: sourceConfig?.metadatas?.token || "",
// systemSelfStart: false,
// systemStartupConnect: false,
// systemSilentStartup: false,
// webEnable: true,
// webPort: sourceConfig?.webServer?.port || 57400,
// transportProtocol: sourceConfig?.transport?.protocol || "tcp",
// transportDialServerTimeout:
// sourceConfig?.transport?.dialServerTimeout || 10,
// transportDialServerKeepalive:
// sourceConfig?.transport?.dialServerKeepalive || 70,
// transportPoolCount: sourceConfig?.transport?.poolCount || 0,
// transportTcpMux: sourceConfig?.transport?.tcpMux || true,
// transportTcpMuxKeepaliveInterval:
// sourceConfig?.transport?.tcpMuxKeepaliveInterval || 7200
// };
// let frpcProxys = [];
// // 解析proxy
// if (sourceConfig?.proxies && sourceConfig.proxies.length > 0) {
// const frpcProxys1 = sourceConfig.proxies.map(m => {
// const rm: Proxy = {
// _id: uuidv4(),
// name: m?.name,
// type: m?.type,
// localIp: m?.localIP || "",
// localPort: m?.localPort || null,
// remotePort: m?.remotePort || null,
// customDomains: m?.customDomains || [],
// subdomain: m.subdomain || "",
// basicAuth: m.basicAuth || false,
// httpUser: m.httpUser || "",
// httpPassword: m.httpPassword || "",
// // 以下为stcp参数
// stcpModel: "visited",
// serverName: "",
// secretKey: m?.secretKey || "",
// bindAddr: "",
// bindPort: null,
// status: m?.status || true,
// fallbackTo: m?.fallbackTo,
// fallbackTimeoutMs: m?.fallbackTimeoutMs || 500,
// keepTunnelOpen: m?.keepTunnelOpen || false,
// https2http: m?.https2http || false,
// https2httpCaFile: m?.https2httpCaFile || "",
// https2httpKeyFile: m?.https2httpKeyFile || ""
// };
// return rm;
// });
// frpcProxys = [...frpcProxys, ...frpcProxys1];
// logInfo(LogModule.APP, "Parsed proxies from configuration.");
// }
// // 解析stcp的访问者
// if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) {
// const frpcProxys2 = sourceConfig.visitors.map(m => {
// const rm: Proxy = {
// _id: uuidv4(),
// name: m?.name,
// type: m?.type,
// localIp: "",
// localPort: null,
// remotePort: null,
// customDomains: [],
// subdomain: m.subdomain || "",
// basicAuth: m.basicAuth || false,
// httpUser: m.httpUser || "",
// httpPassword: m.httpPassword || "",
// // 以下为stcp参数
// stcpModel: "visitors",
// serverName: m?.serverName,
// secretKey: m?.secretKey || "",
// bindAddr: m?.bindAddr,
// bindPort: m?.bindPort,
// status: m?.status || true,
// fallbackTo: m?.fallbackTo,
// fallbackTimeoutMs: m?.fallbackTimeoutMs || 500,
// keepTunnelOpen: m?.keepTunnelOpen || false,
// https2http: m?.https2http || false,
// https2httpCaFile: m?.https2httpCaFile || "",
// https2httpKeyFile: m?.https2httpKeyFile || ""
// };
// return rm;
// });
// frpcProxys = [...frpcProxys, ...frpcProxys2];
// logInfo(LogModule.APP, "Parsed visitors from configuration.");
// }
// if (targetConfig) {
// clearConfig(() => {
// logInfo(LogModule.APP, "Clearing existing configuration.");
// saveConfig(targetConfig);
// logInfo(LogModule.APP, "New configuration saved.");
// });
// }
// if (frpcProxys && frpcProxys.length > 0) {
// clearProxy(() => {
// frpcProxys.forEach(f => {
// insertProxy(f, err => {
// if (err) {
// logError(LogModule.APP, `Error inserting proxy: ${err}`);
// } else {
// logInfo(LogModule.APP, `Inserted proxy: ${JSON.stringify(f)}`);
// }
// });
// });
// });
// }
// };
//
// ipcMain.on("config.importConfig", async (event, args) => {
// logInfo(LogModule.APP, "Attempting to import configuration.");
// const result = await dialog.showOpenDialog(win, {
// properties: ["openFile"],
// filters: [
// { name: "FrpcConfig Files", extensions: ["toml", "ini"] } // 允许选择的文件类型
// ]
// });
// if (result.canceled) {
// logWarn(LogModule.APP, "Import canceled by user.");
// return;
// } else {
// const filePath = result.filePaths[0];
// const fileExtension = path.extname(filePath); // 获取文件后缀名
// logWarn(
// LogModule.APP,
// `Importing file ${filePath} with extension ${fileExtension}`
// );
// if (fileExtension === ".toml") {
// parseTomlConfig(filePath);
// event.reply("Config.importConfig.hook", {
// success: true
// });
// } else {
// logError(
// LogModule.APP,
// `Import failed, unsupported file format: ${fileExtension}`
// );
// event.reply("Config.importConfig.hook", {
// success: false,
// data: `导入失败,暂不支持 ${fileExtension} 格式文件`
// });
// }
// }
// });
//
// ipcMain.on("config.clearAll", async (event, args) => {
// logInfo(LogModule.APP, "Clearing all configurations.");
// stopFrpcProcess(() => {
// clearConfig();
// clearProxy();
// clearVersion();
// event.reply("Config.clearAll.hook", {});
// logInfo(LogModule.APP, "All configurations cleared.");
// });
// });
//
// ipcMain.on("config.openDataFolder", async (event, args) => {
// const userDataPath = app.getPath("userData");
// logInfo(LogModule.APP, "Attempting to open data folder.");
// shell.openPath(userDataPath).then(errorMessage => {
// if (errorMessage) {
// logError(LogModule.APP, `Failed to open data folder: ${errorMessage}`);
// event.reply("Config.openDataFolder.hook", false);
// } else {
// logInfo(LogModule.APP, "Data folder opened successfully.");
// event.reply("Config.openDataFolder.hook", true);
// }
// });
// });
// };

View File

@ -1,21 +1,21 @@
import {dialog, ipcMain} from "electron";
import { logInfo, logError, LogModule } from "../utils/log";
export const initFileApi = () => {
ipcMain.handle("file.selectFile", async (event, args) => {
logInfo(LogModule.APP, `Attempting to open file dialog with filters: ${JSON.stringify(args)}`);
try {
const result = dialog.showOpenDialogSync({
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: args },
]
});
logInfo(LogModule.APP, `File dialog result: ${JSON.stringify(result)}`);
return result;
} catch (error) {
logError(LogModule.APP, `Error opening file dialog: ${error.message}`);
return null;
}
});
}
// import {dialog, ipcMain} from "electron";
// import { logInfo, logError, LogModule } from "../utils/log";
//
// export const initFileApi = () => {
// ipcMain.handle("file.selectFile", async (event, args) => {
// logInfo(LogModule.APP, `Attempting to open file dialog with filters: ${JSON.stringify(args)}`);
// try {
// const result = dialog.showOpenDialogSync({
// properties: ['openFile'],
// filters: [
// { name: 'Text Files', extensions: args },
// ]
// });
// logInfo(LogModule.APP, `File dialog result: ${JSON.stringify(result)}`);
// return result;
// } catch (error) {
// logError(LogModule.APP, `Error opening file dialog: ${error.message}`);
// return null;
// }
// });
// }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,81 +1,81 @@
import {app, dialog, autoUpdater, BrowserWindow} from "electron";
const log = require('electron-log');
export const initUpdaterApi = (win: BrowserWindow) => {
//更新测试打开
Object.defineProperty(app, 'isPackaged', {
get() {
return true;
}
});
const server = 'https://hazel-git-master-uiluck.vercel.app'
let packageName = null
const platform = process.platform;
const arch = process.arch;
switch (platform) {
case "darwin":
if (arch == "arm64") {
packageName = "darwin_arm64";
} else {
packageName = "darwin";
}
break;
case "win32":
packageName = "exe";
break;
case "linux":
packageName = "AppImage";
if (arch == "arm64") {
packageName = "AppImage_arm64";
} else {
packageName = "AppImage";
}
break;
}
const url = `${server}/update/${packageName}/${app.getVersion()}`
log.info(`开启自动更新 ${url}`);
autoUpdater.setFeedURL({url: url})
autoUpdater.on('checking-for-update', () => {
log.info("正在检查更新")
})
autoUpdater.on('update-available', (event, info) => {
log.info(`发现新版本`)
})
autoUpdater.on('update-not-available', () => {
log.info('没有可用的更新')
})
autoUpdater.on('error', (err) => {
log.error(`更新错误:${err.message}`)
})
autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox({
type: 'info',
title: '应用更新',
message: '发现新版本,是否更新?',
buttons: ['是', '否']
}).then((buttonIndex) => {
if (buttonIndex.response == 0) { //选择是,则退出程序,安装新版本
autoUpdater.quitAndInstall()
app.quit()
}
})
})
// setInterval(() => {
// log.initialize("定时检查更新")
// // autoUpdater.checkForUpdates();
// }, 60000)
autoUpdater.checkForUpdates();
log.info("手动检查更新一次")
}
// import {app, dialog, autoUpdater, BrowserWindow} from "electron";
//
// const log = require('electron-log');
//
//
// export const initUpdaterApi = (win: BrowserWindow) => {
// //更新测试打开
// Object.defineProperty(app, 'isPackaged', {
// get() {
// return true;
// }
// });
// const server = 'https://hazel-git-master-uiluck.vercel.app'
// let packageName = null
// const platform = process.platform;
// const arch = process.arch;
// switch (platform) {
// case "darwin":
// if (arch == "arm64") {
// packageName = "darwin_arm64";
// } else {
// packageName = "darwin";
// }
// break;
// case "win32":
// packageName = "exe";
// break;
// case "linux":
// packageName = "AppImage";
// if (arch == "arm64") {
// packageName = "AppImage_arm64";
// } else {
// packageName = "AppImage";
// }
// break;
// }
// const url = `${server}/update/${packageName}/${app.getVersion()}`
// log.info(`开启自动更新 ${url}`);
// autoUpdater.setFeedURL({url: url})
//
// autoUpdater.on('checking-for-update', () => {
// log.info("正在检查更新")
// })
//
// autoUpdater.on('update-available', (event, info) => {
// log.info(`发现新版本`)
// })
//
// autoUpdater.on('update-not-available', () => {
// log.info('没有可用的更新')
//
// })
//
// autoUpdater.on('error', (err) => {
// log.error(`更新错误:${err.message}`)
//
// })
//
// autoUpdater.on('update-downloaded', () => {
// dialog.showMessageBox({
// type: 'info',
// title: '应用更新',
// message: '发现新版本,是否更新?',
// buttons: ['是', '否']
// }).then((buttonIndex) => {
// if (buttonIndex.response == 0) { //选择是,则退出程序,安装新版本
// autoUpdater.quitAndInstall()
// app.quit()
// }
// })
// })
//
// // setInterval(() => {
// // log.initialize("定时检查更新")
// // // autoUpdater.checkForUpdates();
// // }, 60000)
// autoUpdater.checkForUpdates();
// log.info("手动检查更新一次")
//
//
// }

View File

@ -5,6 +5,7 @@ import PathUtils from "../utils/PathUtils";
import fs from "fs";
import FrpcProcessService from "../service/FrpcProcessService";
import SystemService from "../service/SystemService";
import moment from "moment";
class ConfigController extends BaseController {
private readonly _serverService: ServerService;
@ -70,8 +71,11 @@ class ConfigController extends BaseController {
exportConfig(req: ControllerParam) {
this._systemService.openDirectory().then(folder => {
this._serverService.genTomlConfig(folder.filePaths[0]).then(() => {
req.event.reply(req.channel, success());
const path = `${folder.filePaths[0]}/frpc-${moment(new Date()).format(
"YYYYMMDDhhmmss"
)}.toml`;
this._serverService.genTomlConfig(path).then(() => {
req.event.reply(req.channel, success(path));
});
});
}

View File

@ -1,13 +1,13 @@
import BaseController from "./BaseController";
import ProxyService from "../service/ProxyService";
import { success } from "../utils/response";
import ProxyDao from "../dao/ProxyDao";
import ProxyRepository from "../repository/ProxyRepository";
class ProxyController extends BaseController {
private readonly _proxyService: ProxyService;
private readonly _proxyDao: ProxyDao;
private readonly _proxyDao: ProxyRepository;
constructor(proxyService: ProxyService, proxyDao: ProxyDao) {
constructor(proxyService: ProxyService, proxyDao: ProxyRepository) {
super();
this._proxyService = proxyService;
this._proxyDao = proxyDao;

View File

@ -1,13 +1,13 @@
import BaseController from "./BaseController";
import VersionService from "../service/VersionService";
import { fail, success } from "../utils/response";
import VersionDao from "../dao/VersionDao";
import VersionRepository from "../repository/VersionRepository";
class VersionController extends BaseController {
private readonly _versionService: VersionService;
private readonly _versionDao: VersionDao;
private readonly _versionDao: VersionRepository;
constructor(versionService: VersionService, versionDao: VersionDao) {
constructor(versionService: VersionService, versionDao: VersionRepository) {
super();
this._versionService = versionService;
this._versionDao = versionDao;

View File

@ -1,13 +1,23 @@
/**
* todo DI
*/
import Logger from "./Logger";
class BeanFactory {
private static _beans: Map<string, any> = new Map<string, any>();
private static registerBean(name: string, instance: any): void {
if (!this._beans.has(name)) {
this._beans.set(name, instance);
static registerBean(clazz: Function, beanName?: string): void {
if (!beanName) {
beanName = this.getBeanName(clazz.name);
}
if (this.hasBean(beanName)) {
return;
}
const instance = new (clazz as any)();
this._beans.set(beanName, instance);
}
public static setBean<T>(name: string, bean: T): void {
this._beans.set(name, bean);
Logger.info(`register bean ${name} ${bean}`);
}
public static getBean<T>(name: string): T {
@ -21,6 +31,14 @@ class BeanFactory {
public static clear(): void {
this._beans.clear();
}
public static getBeanName(className: string) {
return className.charAt(0).toLowerCase() + className.slice(1);
}
}
export default BeanFactory;

View File

@ -1,6 +1,6 @@
import { app } from "electron";
class GlobalConstant {
public static APP_NAME = "Frpc Desktop";
public static ZIP_EXT = ".zip";
public static GZ_EXT = ".gz";
public static TAR_GZ_EXT = ".tar.gz";

View File

@ -1,20 +1,3 @@
import ConfigController from "../controller/ConfigController";
import ServerDao from "../dao/ServerDao";
import ServerService from "../service/ServerService";
import LogService from "../service/LogService";
import VersionService from "../service/VersionService";
import { BrowserWindow, ipcMain } from "electron";
import LogController from "../controller/LogController";
import VersionController from "../controller/VersionController";
import VersionDao from "../dao/VersionDao";
import GitHubService from "../service/GitHubService";
import FrpcProcessService from "../service/FrpcProcessService";
import LaunchController from "../controller/LaunchController";
import ProxyDao from "../dao/ProxyDao";
import ProxyService from "../service/ProxyService";
import ProxyController from "../controller/ProxyController";
import SystemService from "../service/SystemService";
import SystemController from "../controller/SystemController";
export const ipcRouters: IpcRouters = {
SERVER: {
@ -133,119 +116,3 @@ export const listeners: Listeners = {
channel: "frpcProcess:watchFrpcLog"
}
};
class IpcRouterConfigurate {
ipcRouters: Array<IpcRouter>;
private readonly _beans: Map<string, any> = new Map<string, any>();
private readonly _win: BrowserWindow;
/**
* initBeans
* @private
*/
private initializeBeans() {
const serverDao = new ServerDao();
const versionDao = new VersionDao();
const proxyDao = new ProxyDao();
const systemService = new SystemService();
const serverService = new ServerService(serverDao, proxyDao);
const gitHubService = new GitHubService();
const versionService = new VersionService(
versionDao,
systemService,
gitHubService
);
const logService = new LogService(systemService);
const frpcProcessService = new FrpcProcessService(
serverService,
versionDao
);
const proxyService = new ProxyService(proxyDao);
const configController = new ConfigController(
serverService,
systemService,
frpcProcessService
);
const versionController = new VersionController(versionService, versionDao);
const logController = new LogController(logService);
const launchController = new LaunchController(frpcProcessService);
const proxyController = new ProxyController(proxyService, proxyDao);
const systemController = new SystemController(systemService);
this._beans.set("serverDao", serverDao);
this._beans.set("versionDao", versionDao);
this._beans.set("proxyDao", proxyDao);
this._beans.set("systemService", systemService);
this._beans.set("serverService", serverService);
this._beans.set("versionService", versionService);
this._beans.set("logService", logService);
this._beans.set("proxyService", proxyService);
this._beans.set("frpcProcessService", frpcProcessService);
this._beans.set("configController", configController);
this._beans.set("versionController", versionController);
this._beans.set("logController", logController);
this._beans.set("launchController", launchController);
this._beans.set("proxyController", proxyController);
this._beans.set("systemController", systemController);
}
/**
* initJob
* @private
*/
private initializeListeners() {
Object.keys(listeners).forEach(listenerKey => {
console.log(listenerKey, "listenerKey", listeners[listenerKey]);
const { listenerMethod, channel } = listeners[listenerKey];
const [beanName, method] = listenerMethod.split(".");
const bean = this._beans.get(beanName);
const listenerParam: ListenerParam = {
win: this._win,
channel: channel,
args: []
};
bean[method].call(bean, listenerParam);
});
console.log("initialize listeners success");
// this._beans.get("logService").watchFrpcLog(this._win);
}
/**
* initRouters
* @private
*/
private initializeRouters() {
Object.keys(ipcRouters).forEach(routerKey => {
const routerGroup = ipcRouters[routerKey];
Object.keys(routerGroup).forEach(method => {
const router = routerGroup[method];
ipcMain.on(router.path, (event, args) => {
const req: ControllerParam = {
win: this._win,
channel: `${router.path}:hook`,
event: event,
args: args
};
const [beanName, method] = router.controller.split(".");
const bean = this._beans.get(beanName);
bean[method].call(bean, req);
// bean[method].call(bean, req);
});
});
});
}
/**
* constructor
* @param win mainWindows
*/
constructor(win: BrowserWindow) {
this._win = win;
this.initializeBeans();
this.initializeListeners();
this.initializeRouters();
}
}
export default IpcRouterConfigurate;

18
electron/core/Logger.ts Normal file
View File

@ -0,0 +1,18 @@
import log from "electron-log";
class Logger {
static {
log.transports.file.level = "debug";
log.transports.console.level = "debug";
}
static info(msg: string) {}
static debug(msg: string) {}
static warn(msg: string) {}
static error(msg: string) {}
}
export default Logger;

View File

@ -0,0 +1,25 @@
// // Class decorator
// import "reflect-metadata";
// import BeanFactory from "../BeanFactory";
//
// const Component =
// (): ClassDecorator =>
// target => {
// // BeanFactory.registerBean(
// // beanName || BeanFactory.getBeanName(target.name),
// // target
// // );
// };
//
//
//
//
// export default Component;
//
//
// // export default function Component(beanName?: string): ClassDecorator {
// // return function (target) {
// // const paramtypes = Reflect.getMetadata('design:paramtypes', target);
// // console.log(paramtypes);
// // };
// // }

View File

@ -0,0 +1,5 @@
// export default function Resource(beanName?: string): PropertyDecorator {
// return function (target: Object, propertyKey: string | symbol) {
// console.log(target, propertyKey);
// };
// }

View File

@ -10,14 +10,25 @@ import {
} from "electron";
import { release } from "node:os";
import node_path, { join } from "node:path";
import { initGitHubApi } from "../api/github";
import { initConfigApi } from "../api/config";
import { startFrpWorkerProcess, stopFrpcProcess } from "../api/frpc";
import { initFileApi } from "../api/file";
import { getConfig } from "../storage/config";
import { initLog, logError, logInfo, LogModule } from "../utils/log";
import { maskSensitiveData } from "../utils/desensitize";
import IpcRouterConfigurate from "../core/IpcRouter";
import BeanFactory from "../core/BeanFactory";
import ServerRepository from "../repository/ServerRepository";
import VersionRepository from "../repository/VersionRepository";
import ProxyRepository from "../repository/ProxyRepository";
import SystemService from "../service/SystemService";
import ServerService from "../service/ServerService";
import GitHubService from "../service/GitHubService";
import VersionService from "../service/VersionService";
import LogService from "../service/LogService";
import FrpcProcessService from "../service/FrpcProcessService";
import ProxyService from "../service/ProxyService";
import ConfigController from "../controller/ConfigController";
import VersionController from "../controller/VersionController";
import LogController from "../controller/LogController";
import LaunchController from "../controller/LaunchController";
import ProxyController from "../controller/ProxyController";
import SystemController from "../controller/SystemController";
import { ipcRouters, listeners } from "../core/IpcRouter";
import Logger from "../core/Logger";
process.env.DIST_ELECTRON = join(__dirname, "..");
process.env.DIST = join(process.env.DIST_ELECTRON, "../dist");
@ -25,260 +36,351 @@ process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
? join(process.env.DIST_ELECTRON, "../public")
: process.env.DIST;
let win: BrowserWindow | null = null;
let tray = null;
const preload = join(__dirname, "../preload/index.js");
const url = process.env.VITE_DEV_SERVER_URL;
const indexHtml = join(process.env.DIST, "index.html");
let isQuiting;
// Disable GPU Acceleration for Windows 7
if (release().startsWith("6.1")) app.disableHardwareAcceleration();
// Set application name for Windows 10+ notifications
if (process.platform === "win32") app.setAppUserModelId(app.getName());
class FrpcDesktopApp {
private _win: BrowserWindow | null = null;
private readonly _silentStart = false;
private _quitting = false;
if (!app.requestSingleInstanceLock()) {
app.quit();
process.exit(0);
}
async function createWindow(config: FrpConfig) {
let show = true;
if (config) {
show = !config.systemSilentStartup;
}
win = new BrowserWindow({
title: "Frpc Desktop",
icon: join(process.env.VITE_PUBLIC, "logo/only/16x16.png"),
width: 800,
height: 600,
minWidth: 800,
minHeight: 600,
maxWidth: 1280,
maxHeight: 960,
webPreferences: {
preload,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
// Consider using contextBridge.exposeInMainWorld
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
nodeIntegration: true,
contextIsolation: false
},
show: show
});
if (process.env.VITE_DEV_SERVER_URL) {
// electron-vite-vue#298
win.loadURL(url);
// Open devTool if the app is not packaged
win.webContents.openDevTools();
} else {
win.loadFile(indexHtml);
constructor() {
this.initializeBeans();
this.initializeListeners();
this.initializeRouters();
this.initializeElectronApp();
}
// Test actively push message to the Electron-Renderer
win.webContents.on("did-finish-load", () => {
win?.webContents.send("main-process-message", new Date().toLocaleString());
});
// Make all links open with the browser, not with the application
win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith("https:")) shell.openExternal(url);
return { action: "deny" };
});
// 隐藏菜单栏
const { Menu } = require("electron");
Menu.setApplicationMenu(null);
// hide menu for Mac
// if (process.platform !== "darwin") {
// app.dock.hide();
// }
win.on("minimize", function (event) {
event.preventDefault();
win.hide();
});
win.on("close", function (event) {
if (!isQuiting) {
event.preventDefault();
win.hide();
if (process.platform === "darwin") {
app.dock.hide();
}
}
return false;
});
}
export const createTray = (config: FrpConfig) => {
let menu: Array<MenuItemConstructorOptions | MenuItem> = [
{
label: "显示主窗口",
click: function () {
win.show();
if (process.platform === "darwin") {
app.dock.show();
}
}
},
{
label: "退出",
click: () => {
isQuiting = true;
stopFrpcProcess(() => {
app.quit();
});
}
}
];
tray = new Tray(
node_path.join(process.env.VITE_PUBLIC, "logo/only/16x16.png")
);
tray.setToolTip("Frpc Desktop");
const contextMenu = Menu.buildFromTemplate(menu);
tray.setContextMenu(contextMenu);
// 托盘双击打开
tray.on("double-click", () => {
win.show();
});
logInfo(LogModule.APP, `Tray created successfully.`);
};
app.whenReady().then(() => {
initLog();
logInfo(
LogModule.APP,
`Application started. Current system architecture: ${
process.arch
}, platform: ${process.platform}, version: ${app.getVersion()}.`
);
getConfig((err, config) => {
if (err) {
logError(LogModule.APP, `Failed to get config: ${err.message}`);
initializeWindow() {
if (this._win) {
return;
}
this._win = new BrowserWindow({
title: app.getName(),
icon: join(process.env.VITE_PUBLIC, "logo/only/16x16.png"),
width: 800,
height: 600,
minWidth: 800,
minHeight: 600,
maxWidth: 1280,
maxHeight: 960,
webPreferences: {
preload,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
// Consider using contextBridge.exposeInMainWorld
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
nodeIntegration: true,
contextIsolation: false
},
show: true
});
BeanFactory.setBean("win", this._win);
if (process.env.VITE_DEV_SERVER_URL) {
// electron-vite-vue#298
this._win.loadURL(url).then(() => {});
// Open devTool if the app is not packaged
this._win.webContents.openDevTools();
} else {
this._win.loadFile(indexHtml).then(() => {});
}
createWindow(config)
.then(r => {
logInfo(LogModule.APP, `Window created successfully.`);
createTray(config);
this._win.webContents.on("did-finish-load", () => {
this._win?.webContents.send(
"main-process-message",
new Date().toLocaleString()
);
});
this._win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith("https:")) shell.openExternal(url);
return { action: "deny" };
});
const { Menu } = require("electron");
Menu.setApplicationMenu(null);
if (config) {
logInfo(
LogModule.APP,
`Config retrieved: ${JSON.stringify(
maskSensitiveData(config, [
"serverAddr",
"serverPort",
"authToken",
"user",
"metaToken"
])
)}`
);
const that = this;
this._win.on("minimize", function (event) {
event.preventDefault();
that._win.hide();
});
if (config.systemStartupConnect) {
startFrpWorkerProcess(config);
this._win.on("close", function (event) {
if (!that._quitting) {
event.preventDefault();
that._win.hide();
if (process.platform === "darwin") {
app.dock.hide();
}
}
return false;
});
}
initializeTray() {
const that = this;
let menu: Array<MenuItemConstructorOptions | MenuItem> = [
{
label: "显示主窗口",
click: function () {
that._win.show();
if (process.platform === "darwin") {
app.dock.show().then(() => {});
}
}
const ipcRouterConfig = new IpcRouterConfigurate(win);
// Initialize APIs
try {
initGitHubApi(win);
logInfo(LogModule.APP, `GitHub API initialized.`);
initConfigApi(win);
logInfo(LogModule.APP, `Config API initialized.`);
initFileApi();
logInfo(LogModule.APP, `File API initialized.`);
// initUpdaterApi(win);
logInfo(LogModule.APP, `Updater API initialization skipped.`);
} catch (error) {
logError(
LogModule.APP,
`Error during API initialization: ${error.message}`
);
},
{
label: "退出",
click: () => {
that._quitting = true;
// todo stop frpc process
app.quit();
}
})
.catch(error => {
logError(LogModule.APP, `Error creating window: ${error.message}`);
});
});
});
app.on("window-all-closed", () => {
logInfo(LogModule.APP, `All windows closed.`);
win = null;
if (process.platform !== "darwin") {
stopFrpcProcess(() => {
logInfo(LogModule.APP, `FRPC process stopped. Quitting application.`);
app.quit();
});
}
});
app.on("second-instance", () => {
logInfo(LogModule.APP, `Second instance detected.`);
if (win) {
// Focus on the main window if the user tried to open another
if (win.isMinimized()) win.restore();
win.focus();
}
});
app.on("activate", () => {
logInfo(LogModule.APP, `Application activated.`);
const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length) {
allWindows[0].focus();
} else {
getConfig((err, config) => {
if (err) {
logError(
LogModule.APP,
`Failed to get config on activate: ${err.message}`
);
return;
}
createWindow(config).then(r => {
logInfo(LogModule.APP, `Window created on activate.`);
});
];
const tray = new Tray(
node_path.join(process.env.VITE_PUBLIC, "logo/only/16x16.png")
);
tray.setToolTip(app.getName());
const contextMenu = Menu.buildFromTemplate(menu);
tray.setContextMenu(contextMenu);
// 托盘双击打开
tray.on("double-click", () => {
this._win.show();
});
}
});
app.on("before-quit", () => {
logInfo(LogModule.APP, `Application is about to quit.`);
stopFrpcProcess(() => {
isQuiting = true;
});
});
initializeElectronApp() {
// Disable GPU Acceleration for Windows 7
if (release().startsWith("6.1")) app.disableHardwareAcceleration();
ipcMain.handle("open-win", (_, arg) => {
logInfo(LogModule.APP, `Opening new window with argument: ${arg}`);
const childWindow = new BrowserWindow({
webPreferences: {
preload,
nodeIntegration: true,
contextIsolation: false
// Set application name for Windows 10+ notifications
if (process.platform === "win32") app.setAppUserModelId(app.getName());
if (!app.requestSingleInstanceLock()) {
app.quit();
process.exit(0);
}
});
app.whenReady().then(() => {
this.initializeWindow();
this.initializeTray();
// initLog();
// logInfo(
// LogModule.APP,
// `Application started. Current system architecture: ${
// process.arch
// }, platform: ${process.platform}, version: ${app.getVersion()}.`
// );
if (process.env.VITE_DEV_SERVER_URL) {
childWindow.loadURL(`${url}#${arg}`);
logInfo(LogModule.APP, `Child window loaded URL: ${url}#${arg}`);
} else {
childWindow.loadFile(indexHtml, { hash: arg });
logInfo(
LogModule.APP,
`Child window loaded file: ${indexHtml} with hash: ${arg}`
// getConfig((err, config) => {
// if (err) {
// logError(LogModule.APP, `Failed to get config: ${err.message}`);
// return;
// }
// createWindow(config)
// .then(r => {
// logInfo(LogModule.APP, `Window created successfully.`);
// createTray(config);
//
// // if (config) {
// // logInfo(
// // LogModule.APP,
// // `Config retrieved: ${JSON.stringify(
// // maskSensitiveData(config, [
// // "serverAddr",
// // "serverPort",
// // "authToken",
// // "user",
// // "metaToken"
// // ])
// // )}`
// // );
// //
// // if (config.systemStartupConnect) {
// // startFrpWorkerProcess(config);
// // }
// // }
// // const ipcRouterConfig = new IpcRouterConfigurate(win);
// // Initialize APIs
// // try {
// // initGitHubApi(win);
// // logInfo(LogModule.APP, `GitHub API initialized.`);
// //
// // initConfigApi(win);
// // logInfo(LogModule.APP, `Config API initialized.`);
// //
// // initFileApi();
// // logInfo(LogModule.APP, `File API initialized.`);
// //
// // // initUpdaterApi(win);
// // logInfo(LogModule.APP, `Updater API initialization skipped.`);
// // } catch (error) {
// // logError(
// // LogModule.APP,
// // `Error during API initialization: ${error.message}`
// // );
// // }
// })
// .catch(error => {
// logError(LogModule.APP, `Error creating window: ${error.message}`);
// });
// });
});
app.on("window-all-closed", () => {
// logInfo(LogModule.APP, `All windows closed.`);
this._win = null;
if (process.platform !== "darwin") {
// todo stop frpc process
// stopFrpcProcess(() => {
// logInfo(LogModule.APP, `FRPC process stopped. Quitting application.`);
app.quit();
// });
}
});
app.on("second-instance", () => {
if (this._win) {
if (this._win.isMinimized()) this._win.restore();
this._win.focus();
}
});
app.on("activate", () => {
// logInfo(LogModule.APP, `Application activated.`);
const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length) {
allWindows[0].focus();
} else {
this.initializeWindow();
}
});
app.on("before-quit", () => {
// todo stop frpc process
this._quitting = true;
});
}
initializeBeans() {
BeanFactory.setBean("serverRepository", new ServerRepository());
BeanFactory.setBean("versionRepository", new VersionRepository());
BeanFactory.setBean("proxyRepository", new ProxyRepository());
BeanFactory.setBean("systemService", new SystemService());
BeanFactory.setBean(
"serverService",
new ServerService(
BeanFactory.getBean("serverRepository"),
BeanFactory.getBean("proxyRepository")
)
);
BeanFactory.setBean("gitHubService", new GitHubService());
BeanFactory.setBean(
"versionService",
new VersionService(
BeanFactory.getBean("versionRepository"),
BeanFactory.getBean("systemService"),
BeanFactory.getBean("gitHubService")
)
);
BeanFactory.setBean(
"logService",
new LogService(BeanFactory.getBean("systemService"))
);
BeanFactory.setBean(
"frpcProcessService",
new FrpcProcessService(
BeanFactory.getBean("serverService"),
BeanFactory.getBean("versionRepository")
)
);
BeanFactory.setBean(
"proxyService",
new ProxyService(BeanFactory.getBean("proxyRepository"))
);
BeanFactory.setBean(
"configController",
new ConfigController(
BeanFactory.getBean("serverService"),
BeanFactory.getBean("systemService"),
BeanFactory.getBean("frpcProcessService")
)
);
BeanFactory.setBean(
"versionController",
new VersionController(
BeanFactory.getBean("versionService"),
BeanFactory.getBean("versionRepository")
)
);
BeanFactory.setBean(
"logController",
new LogController(BeanFactory.getBean("logService"))
);
BeanFactory.setBean(
"launchController",
new LaunchController(BeanFactory.getBean("frpcProcessService"))
);
BeanFactory.setBean(
"proxyController",
new ProxyController(
BeanFactory.getBean("proxyService"),
BeanFactory.getBean("proxyRepository")
)
);
BeanFactory.setBean(
"systemController",
new SystemController(BeanFactory.getBean("systemService"))
);
}
});
/**
* initJob
* @private
*/
private initializeListeners() {
Object.keys(listeners).forEach(listenerKey => {
console.log(listenerKey, "listenerKey", listeners[listenerKey]);
const { listenerMethod, channel } = listeners[listenerKey];
const [beanName, method] = listenerMethod.split(".");
const bean = BeanFactory.getBean(beanName);
const listenerParam: ListenerParam = {
// win: BeanFactory.getBean("win"),
channel: channel,
args: []
};
bean[method].call(bean, listenerParam);
});
Logger.info("initialize listeners success");
// this._beans.get("logService").watchFrpcLog(this._win);
}
/**
* initRouters
* @private
*/
private initializeRouters() {
Object.keys(ipcRouters).forEach(routerKey => {
const routerGroup = ipcRouters[routerKey];
Object.keys(routerGroup).forEach(method => {
const router = routerGroup[method];
ipcMain.on(router.path, (event, args) => {
const req: ControllerParam = {
// win: BeanFactory.getBean("win"),
channel: `${router.path}:hook`,
event: event,
args: args
};
const [beanName, method] = router.controller.split(".");
const bean = BeanFactory.getBean(beanName);
bean[method].call(bean, req);
});
});
});
}
}
new FrpcDesktopApp();

View File

@ -20,7 +20,7 @@ import IdUtils from "../utils/IdUtils";
// }
class BaseDao<T> {
class BaseRepository<T> {
protected readonly db: Datastore;
constructor(dbName: string) {
@ -135,4 +135,4 @@ class BaseDao<T> {
}
}
export default BaseDao;
export default BaseRepository;

View File

@ -1,6 +1,8 @@
import BaseDao from "./BaseDao";
import BaseRepository from "./BaseRepository";
import Component from "../core/annotation/Component";
class ProxyDao extends BaseDao<FrpcProxy> {
// @Component()
class ProxyRepository extends BaseRepository<FrpcProxy> {
constructor() {
super("proxy");
}
@ -23,4 +25,4 @@ class ProxyDao extends BaseDao<FrpcProxy> {
}
}
export default ProxyDao;
export default ProxyRepository;

View File

@ -1,6 +1,8 @@
import BaseDao from "./BaseDao";
import BaseRepository from "./BaseRepository";
import Component from "../core/annotation/Component";
class ServerDao extends BaseDao<OpenSourceFrpcDesktopServer> {
// @Component()
class ServerRepository extends BaseRepository<OpenSourceFrpcDesktopServer> {
constructor() {
super("server");
}
@ -18,4 +20,4 @@ class ServerDao extends BaseDao<OpenSourceFrpcDesktopServer> {
}
}
export default ServerDao
export default ServerRepository

View File

@ -1,6 +1,8 @@
import BaseDao from "./BaseDao";
import BaseRepository from "./BaseRepository";
import Component from "../core/annotation/Component";
class VersionDao extends BaseDao<FrpcVersion> {
// @Component()
class VersionRepository extends BaseRepository<FrpcVersion> {
constructor() {
super("version");
}
@ -18,7 +20,7 @@ class VersionDao extends BaseDao<FrpcVersion> {
}
exists(githubReleaseId: number): Promise<boolean> {
return new Promise(( resolve, reject) => {
return new Promise((resolve, reject) => {
this.db.count({ githubReleaseId: githubReleaseId }, (err, count) => {
if (err) {
reject(err);
@ -30,4 +32,4 @@ class VersionDao extends BaseDao<FrpcVersion> {
}
}
export default VersionDao;
export default VersionRepository;

View File

@ -1,14 +1,14 @@
import BaseDao from "../dao/BaseDao";
import BaseRepository from "../repository/BaseRepository";
interface BaseServiceInterface<T> {
// dao: BaseDao<T>;
// dao: BaseRepository<T>;
}
class BaseService<T> implements BaseServiceInterface<T> {
// dao: BaseDao<T>;
// dao: BaseRepository<T>;
//
// constructor(dao: BaseDao<T>) {
// constructor(dao: BaseRepository<T>) {
// this.dao = dao;
// }
}

View File

@ -1,18 +1,19 @@
import ServerService from "./ServerService";
import VersionDao from "../dao/VersionDao";
import VersionRepository from "../repository/VersionRepository";
import PathUtils from "../utils/PathUtils";
import GlobalConstant from "../core/GlobalConstant";
import { Notification } from "electron";
import { app, BrowserWindow, Notification } from "electron";
import { success } from "../utils/response";
import treeKill from "tree-kill";
import BeanFactory from "../core/BeanFactory";
class FrpcProcessService {
private readonly _serverService: ServerService;
private readonly _versionDao: VersionDao;
private readonly _versionDao: VersionRepository;
private _frpcProcess: any;
private _frpcProcessListener: any;
constructor(serverService: ServerService, versionDao: VersionDao) {
constructor(serverService: ServerService, versionDao: VersionRepository) {
this._serverService = serverService;
this._versionDao = versionDao;
}
@ -81,7 +82,7 @@ class FrpcProcessService {
console.log("running", running);
if (!running) {
new Notification({
title: GlobalConstant.APP_NAME,
title: app.getName(),
body: "Connection lost, please check the logs for details."
}).show();
// logError(
@ -90,7 +91,8 @@ class FrpcProcessService {
// );
// clearInterval(this._frpcProcessListener);
}
listenerParam.win.webContents.send(
const win: BrowserWindow = BeanFactory.getBean("win");
win.webContents.send(
listenerParam.channel,
success(running)
);

View File

@ -2,6 +2,8 @@ import fs from "fs";
import { success } from "../utils/response";
import PathUtils from "../utils/PathUtils";
import SystemService from "./SystemService";
import BeanFactory from "../core/BeanFactory";
import { BrowserWindow } from "electron";
class LogService {
private readonly _systemService: SystemService;
@ -11,8 +13,11 @@ class LogService {
this._systemService = systemService;
}
getFrpLogContent(): Promise<string> {
async getFrpLogContent() {
return new Promise((resolve, reject) => {
if (!fs.existsSync(this._logPath)) {
resolve("");
}
fs.readFile(this._logPath, "utf-8", (error, data) => {
if (!error) {
resolve(data);
@ -28,11 +33,12 @@ class LogService {
setTimeout(() => this.watchFrpcLog(listenerParam), 1000);
return;
}
console.log('watchFrpcLog succcess');
console.log("watchFrpcLog succcess");
fs.watch(this._logPath, (eventType, filename) => {
if (eventType === "change") {
console.log("change", eventType, listenerParam.channel);
listenerParam.win.webContents.send(
const win: BrowserWindow = BeanFactory.getBean("win");
win.webContents.send(
listenerParam.channel,
success(true)
);

View File

@ -1,11 +1,13 @@
import ProxyDao from "../dao/ProxyDao";
import ProxyRepository from "../repository/ProxyRepository";
import Component from "../core/annotation/Component";
const { exec, spawn } = require("child_process");
class ProxyService {
private readonly _proxyDao: ProxyDao;
constructor(proxyDao: ProxyDao) {
private readonly _proxyDao: ProxyRepository;
constructor(public proxyDao: ProxyRepository) {
this._proxyDao = proxyDao;
}

View File

@ -1,16 +1,16 @@
import BaseService from "./BaseService";
import ServerDao from "../dao/ServerDao";
import ServerRepository from "../repository/ServerRepository";
import TOML from "smol-toml";
import fs from "fs";
import PathUtils from "../utils/PathUtils";
import ProxyDao from "../dao/ProxyDao";
import ProxyRepository from "../repository/ProxyRepository";
class ServerService extends BaseService<OpenSourceFrpcDesktopServer> {
private readonly _serverDao: ServerDao;
private readonly _proxyDao: ProxyDao;
private readonly _serverDao: ServerRepository;
private readonly _proxyDao: ProxyRepository;
private readonly _serverId: string = "1";
constructor(serverDao: ServerDao, proxyDao: ProxyDao) {
constructor(serverDao: ServerRepository, proxyDao: ProxyRepository) {
super();
this._serverDao = serverDao;
this._proxyDao = proxyDao;

View File

@ -4,6 +4,7 @@ import path from "path";
import fs from "fs";
import zlib from "zlib";
import admZip from "adm-zip";
import Component from "../core/annotation/Component";
const tar = require("tar");

View File

@ -1,4 +1,4 @@
import VersionDao from "../dao/VersionDao";
import VersionRepository from "../repository/VersionRepository";
import BaseService from "./BaseService";
import GitHubService from "./GitHubService";
import frpReleasesJson from "../json/frp-releases.json";
@ -14,14 +14,14 @@ import frpChecksums from "../json/frp_all_sha256_checksums.json";
import SystemService from "./SystemService";
class VersionService extends BaseService<FrpcVersion> {
private readonly _versionDao: VersionDao;
private readonly _versionDao: VersionRepository;
private readonly _systemService: SystemService;
private readonly _gitHubService: GitHubService;
private readonly _currFrpArch: Array<string>;
private _versions: Array<FrpcVersion> = [];
constructor(
versionDao: VersionDao,
versionDao: VersionRepository,
systemService: SystemService,
gitHubService: GitHubService
) {

View File

@ -64,9 +64,9 @@
"sass": "^1.66.1",
"tailwindcss": "^3.3.3",
"tree-kill": "^1.2.2",
"typescript": "^5.1.6",
"vite": "^4.4.9",
"vite-plugin-electron": "^0.15.3",
"typescript": "5.7.3",
"vite": "^5.4.11",
"vite-plugin-electron": "^0.28.6",
"vite-plugin-electron-renderer": "^0.14.5",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
@ -82,10 +82,10 @@
"intro.js": "^8.0.0-beta.1",
"isbinaryfile": "4.0.10",
"js-base64": "^3.7.7",
"smol-toml": "^1.3.1",
"snowflakify": "^1.0.5",
"tar": "^6.2.0",
"unused-filename": "^4.0.1",
"uuid": "^10.0.0",
"smol-toml": "^1.3.1"
"uuid": "^10.0.0"
}
}

View File

@ -362,21 +362,22 @@ onMounted(() => {
});
});
on(ipcRouters.SERVER.exportConfig, () => {
//
confetti({
zIndex: 12002,
particleCount: 200,
spread: 70,
origin: { y: 0.6 }
});
ElMessageBox.alert("🎉 恭喜你,导入成功 请重启软件", `提示`, {
closeOnClickModal: false,
showClose: false,
confirmButtonText: "立即重启"
}).then(() => {
send(ipcRouters.SYSTEM.relaunchApp);
});
on(ipcRouters.SERVER.exportConfig, (data) => {
ElMessageBox.alert(`配置路径:${data}`, `🎉 导出成功`);
// //
// confetti({
// zIndex: 12002,
// particleCount: 200,
// spread: 70,
// origin: { y: 0.6 }
// });
// ElMessageBox.alert("🎉 ", ``, {
// closeOnClickModal: false,
// showClose: false,
// confirmButtonText: ""
// }).then(() => {
// send(ipcRouters.SYSTEM.relaunchApp);
// });
});
// ElMessageBox.alert(data, ``);
on(ipcRouters.SYSTEM.openAppData, () => {
@ -514,7 +515,6 @@ onUnmounted(() => {
removeRouterListeners(ipcRouters.SERVER.resetAllConfig);
removeRouterListeners(ipcRouters.VERSION.getDownloadedVersions);
removeRouterListeners(ipcRouters.SERVER.exportConfig);
// ipcRenderer.removeAllListeners("Config.clearAll.hook");
removeRouterListeners(ipcRouters.SYSTEM.openAppData);
});
</script>

View File

@ -22,7 +22,7 @@
]
},
"types": [
// "node",
// "node",
"vite/client",
"element-plus/global"
],

4
types/core.d.ts vendored
View File

@ -5,14 +5,14 @@ interface ApiResponse<T> {
}
interface ControllerParam {
win: BrowserWindow;
// win: BrowserWindow;
channel: string;
event: Electron.IpcMainEvent;
args: any;
}
interface ListenerParam {
win: BrowserWindow;
// win: BrowserWindow;
channel: string;
args: any[];
}