mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 01:10:16 +08:00
refactor: improve code formatting and consistency across components
- Updated button and span elements in ThemeToggle.vue and TopActions.vue for better readability. - Enhanced UninstallConfirmModal.vue and UpdateAppsModal.vue with consistent indentation and spacing. - Refactored downloadStatus.ts and storeConfig.ts for improved code clarity. - Standardized string quotes and spacing in typedefinition.ts and processInstall.ts. - Ensured consistent use of arrow functions and improved variable declarations throughout the codebase.
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { ipcMain, WebContents } from 'electron';
|
||||
import { spawn, ChildProcess, exec } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import pino from 'pino';
|
||||
import { ipcMain, WebContents } from "electron";
|
||||
import { spawn, ChildProcess, exec } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import pino from "pino";
|
||||
|
||||
import { ChannelPayload, InstalledAppInfo } from '../../typedefinition';
|
||||
import { ChannelPayload, InstalledAppInfo } from "../../typedefinition";
|
||||
|
||||
const logger = pino({ 'name': 'install-manager' });
|
||||
const logger = pino({ name: "install-manager" });
|
||||
|
||||
type InstallTask = {
|
||||
id: number;
|
||||
@@ -16,67 +16,69 @@ type InstallTask = {
|
||||
webContents: WebContents | null;
|
||||
};
|
||||
|
||||
const SHELL_CALLER_PATH = '/opt/apm-store/extras/shell-caller.sh';
|
||||
const SHELL_CALLER_PATH = "/opt/apm-store/extras/shell-caller.sh";
|
||||
|
||||
export const tasks = new Map<number, InstallTask>();
|
||||
|
||||
let idle = true; // Indicates if the installation manager is idle
|
||||
|
||||
const checkSuperUserCommand = async (): Promise<string> => {
|
||||
let superUserCmd = '';
|
||||
let superUserCmd = "";
|
||||
const execAsync = promisify(exec);
|
||||
if (process.getuid && process.getuid() !== 0) {
|
||||
const { stdout, stderr } = await execAsync('which /usr/bin/pkexec');
|
||||
const { stdout, stderr } = await execAsync("which /usr/bin/pkexec");
|
||||
if (stderr) {
|
||||
logger.error('没有找到 pkexec 命令');
|
||||
logger.error("没有找到 pkexec 命令");
|
||||
return;
|
||||
}
|
||||
logger.info(`找到提升权限命令: ${stdout.trim()}`);
|
||||
superUserCmd = stdout.trim();
|
||||
|
||||
if (superUserCmd.length === 0) {
|
||||
logger.error('没有找到提升权限的命令 pkexec!');
|
||||
logger.error("没有找到提升权限的命令 pkexec!");
|
||||
}
|
||||
}
|
||||
return superUserCmd;
|
||||
}
|
||||
};
|
||||
|
||||
const runCommandCapture = async (execCommand: string, execParams: string[]) => {
|
||||
return await new Promise<{ code: number; stdout: string; stderr: string }>((resolve) => {
|
||||
const child = spawn(execCommand, execParams, {
|
||||
shell: true,
|
||||
env: process.env
|
||||
});
|
||||
return await new Promise<{ code: number; stdout: string; stderr: string }>(
|
||||
(resolve) => {
|
||||
const child = spawn(execCommand, execParams, {
|
||||
shell: true,
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
child.stdout?.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
child.stdout?.on("data", (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
child.stderr?.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
child.stderr?.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
resolve({ code: -1, stdout, stderr: err.message });
|
||||
});
|
||||
child.on("error", (err) => {
|
||||
resolve({ code: -1, stdout, stderr: err.message });
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
resolve({ code: typeof code === 'number' ? code : -1, stdout, stderr });
|
||||
});
|
||||
});
|
||||
child.on("close", (code) => {
|
||||
resolve({ code: typeof code === "number" ? code : -1, stdout, stderr });
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const parseInstalledList = (output: string) => {
|
||||
const apps: Array<InstalledAppInfo> = [];
|
||||
const lines = output.split('\n');
|
||||
const lines = output.split("\n");
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
if (trimmed.startsWith('Listing')) continue;
|
||||
if (trimmed.startsWith('[INFO]')) continue;
|
||||
if (trimmed.startsWith("Listing")) continue;
|
||||
if (trimmed.startsWith("[INFO]")) continue;
|
||||
|
||||
const match = trimmed.match(/^(\S+)\/\S+,\S+\s+(\S+)\s+(\S+)\s+\[(.+)\]$/);
|
||||
if (!match) continue;
|
||||
@@ -85,32 +87,40 @@ const parseInstalledList = (output: string) => {
|
||||
version: match[2],
|
||||
arch: match[3],
|
||||
flags: match[4],
|
||||
raw: trimmed
|
||||
raw: trimmed,
|
||||
});
|
||||
}
|
||||
return apps;
|
||||
};
|
||||
|
||||
const parseUpgradableList = (output: string) => {
|
||||
const apps: Array<{ pkgname: string; newVersion: string; currentVersion: string; raw: string }> = [];
|
||||
const lines = output.split('\n');
|
||||
const apps: Array<{
|
||||
pkgname: string;
|
||||
newVersion: string;
|
||||
currentVersion: string;
|
||||
raw: string;
|
||||
}> = [];
|
||||
const lines = output.split("\n");
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
if (trimmed.startsWith('Listing')) continue;
|
||||
if (trimmed.startsWith('[INFO]')) continue;
|
||||
if (trimmed.includes('=') && !trimmed.includes('/')) continue;
|
||||
if (trimmed.startsWith("Listing")) continue;
|
||||
if (trimmed.startsWith("[INFO]")) continue;
|
||||
if (trimmed.includes("=") && !trimmed.includes("/")) continue;
|
||||
|
||||
if (!trimmed.includes('/')) continue;
|
||||
if (!trimmed.includes("/")) continue;
|
||||
|
||||
const tokens = trimmed.split(/\s+/);
|
||||
if (tokens.length < 2) continue;
|
||||
const pkgToken = tokens[0];
|
||||
const pkgname = pkgToken.split('/')[0];
|
||||
const newVersion = tokens[1] || '';
|
||||
const currentMatch = trimmed.match(/\[(?:upgradable from|from):\s*([^\]\s]+)\]/i);
|
||||
const currentToken = tokens[5] || '';
|
||||
const currentVersion = currentMatch?.[1] || currentToken.replace('[', '').replace(']', '');
|
||||
const pkgname = pkgToken.split("/")[0];
|
||||
const newVersion = tokens[1] || "";
|
||||
const currentMatch = trimmed.match(
|
||||
/\[(?:upgradable from|from):\s*([^\]\s]+)\]/i,
|
||||
);
|
||||
const currentToken = tokens[5] || "";
|
||||
const currentVersion =
|
||||
currentMatch?.[1] || currentToken.replace("[", "").replace("]", "");
|
||||
|
||||
if (!pkgname) continue;
|
||||
apps.push({ pkgname, newVersion, currentVersion, raw: trimmed });
|
||||
@@ -119,30 +129,30 @@ const parseUpgradableList = (output: string) => {
|
||||
};
|
||||
|
||||
// Listen for download requests from renderer process
|
||||
ipcMain.on('queue-install', async (event, download_json) => {
|
||||
ipcMain.on("queue-install", async (event, download_json) => {
|
||||
const download = JSON.parse(download_json);
|
||||
const { id, pkgname } = download || {};
|
||||
|
||||
if (!id || !pkgname) {
|
||||
logger.warn('passed arguments missing id or pkgname');
|
||||
logger.warn("passed arguments missing id or pkgname");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`收到下载任务: ${id}, 软件包名称: ${pkgname}`);
|
||||
|
||||
|
||||
// 避免重复添加同一任务,但允许重试下载
|
||||
if (tasks.has(id) && !download.retry) {
|
||||
tasks.get(id)?.webContents.send('install-log', {
|
||||
tasks.get(id)?.webContents.send("install-log", {
|
||||
id,
|
||||
time: Date.now(),
|
||||
message: `任务id: ${id} 已在列表中,忽略重复添加`
|
||||
message: `任务id: ${id} 已在列表中,忽略重复添加`,
|
||||
});
|
||||
tasks.get(id)?.webContents.send('install-complete', {
|
||||
tasks.get(id)?.webContents.send("install-complete", {
|
||||
id: id,
|
||||
success: false,
|
||||
time: Date.now(),
|
||||
exitCode: -1,
|
||||
message: `{"message":"任务id: ${id} 已在列表中,忽略重复添加","stdout":"","stderr":""}`
|
||||
message: `{"message":"任务id: ${id} 已在列表中,忽略重复添加","stdout":"","stderr":""}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -151,7 +161,7 @@ ipcMain.on('queue-install', async (event, download_json) => {
|
||||
|
||||
// 开始组装安装命令
|
||||
const superUserCmd = await checkSuperUserCommand();
|
||||
let execCommand = '';
|
||||
let execCommand = "";
|
||||
const execParams = [];
|
||||
if (superUserCmd.length > 0) {
|
||||
execCommand = superUserCmd;
|
||||
@@ -159,7 +169,7 @@ ipcMain.on('queue-install', async (event, download_json) => {
|
||||
} else {
|
||||
execCommand = SHELL_CALLER_PATH;
|
||||
}
|
||||
execParams.push('apm', 'install', '-y', pkgname);
|
||||
execParams.push("apm", "install", "-y", pkgname);
|
||||
|
||||
const task: InstallTask = {
|
||||
id,
|
||||
@@ -167,7 +177,7 @@ ipcMain.on('queue-install', async (event, download_json) => {
|
||||
execCommand,
|
||||
execParams,
|
||||
process: null,
|
||||
webContents
|
||||
webContents,
|
||||
};
|
||||
tasks.set(id, task);
|
||||
if (idle) processNextInQueue(0);
|
||||
@@ -179,53 +189,53 @@ function processNextInQueue(index: number) {
|
||||
idle = false;
|
||||
const task = Array.from(tasks.values())[index];
|
||||
const webContents = task.webContents;
|
||||
let stdoutData = '';
|
||||
let stderrData = '';
|
||||
let stdoutData = "";
|
||||
let stderrData = "";
|
||||
|
||||
webContents.send('install-status', {
|
||||
webContents.send("install-status", {
|
||||
id: task.id,
|
||||
time: Date.now(),
|
||||
message: 'installing'
|
||||
})
|
||||
webContents.send('install-log', {
|
||||
id: task.id,
|
||||
time: Date.now(),
|
||||
message: `开始执行: ${task.execCommand} ${task.execParams.join(' ')}`
|
||||
message: "installing",
|
||||
});
|
||||
logger.info(`启动安装命令: ${task.execCommand} ${task.execParams.join(' ')}`);
|
||||
webContents.send("install-log", {
|
||||
id: task.id,
|
||||
time: Date.now(),
|
||||
message: `开始执行: ${task.execCommand} ${task.execParams.join(" ")}`,
|
||||
});
|
||||
logger.info(`启动安装命令: ${task.execCommand} ${task.execParams.join(" ")}`);
|
||||
|
||||
const child = spawn(task.execCommand, task.execParams, {
|
||||
shell: true,
|
||||
env: process.env
|
||||
env: process.env,
|
||||
});
|
||||
task.process = child;
|
||||
|
||||
// 监听 stdout
|
||||
child.stdout.on('data', (data) => {
|
||||
child.stdout.on("data", (data) => {
|
||||
stdoutData += data.toString();
|
||||
webContents.send('install-log', {
|
||||
webContents.send("install-log", {
|
||||
id: task.id,
|
||||
time: Date.now(),
|
||||
message: data.toString()
|
||||
message: data.toString(),
|
||||
});
|
||||
});
|
||||
|
||||
// 监听 stderr
|
||||
child.stderr.on('data', (data) => {
|
||||
child.stderr.on("data", (data) => {
|
||||
stderrData += data.toString();
|
||||
webContents.send('install-log', {
|
||||
webContents.send("install-log", {
|
||||
id: task.id,
|
||||
time: Date.now(),
|
||||
message: data.toString()
|
||||
message: data.toString(),
|
||||
});
|
||||
});
|
||||
child.on('close', (code) => {
|
||||
child.on("close", (code) => {
|
||||
const success = code === 0;
|
||||
// 拼接json消息
|
||||
const messageJSONObj = {
|
||||
message: success ? '安装完成' : `安装失败,退出码 ${code}`,
|
||||
message: success ? "安装完成" : `安装失败,退出码 ${code}`,
|
||||
stdout: stdoutData,
|
||||
stderr: stderrData
|
||||
stderr: stderrData,
|
||||
};
|
||||
|
||||
if (success) {
|
||||
@@ -234,42 +244,45 @@ function processNextInQueue(index: number) {
|
||||
logger.error(messageJSONObj);
|
||||
}
|
||||
|
||||
webContents.send('install-complete', {
|
||||
webContents.send("install-complete", {
|
||||
id: task.id,
|
||||
success: success,
|
||||
time: Date.now(),
|
||||
exitCode: code,
|
||||
message: JSON.stringify(messageJSONObj)
|
||||
message: JSON.stringify(messageJSONObj),
|
||||
});
|
||||
tasks.delete(task.id);
|
||||
idle = true;
|
||||
if (tasks.size > 0)
|
||||
processNextInQueue(0);
|
||||
if (tasks.size > 0) processNextInQueue(0);
|
||||
});
|
||||
}
|
||||
|
||||
ipcMain.handle('check-installed', async (_event, pkgname: string) => {
|
||||
ipcMain.handle("check-installed", async (_event, pkgname: string) => {
|
||||
if (!pkgname) {
|
||||
logger.warn('check-installed missing pkgname');
|
||||
logger.warn("check-installed missing pkgname");
|
||||
return false;
|
||||
}
|
||||
let isInstalled = false;
|
||||
|
||||
logger.info(`检查应用是否已安装: ${pkgname}`);
|
||||
|
||||
const child = spawn(SHELL_CALLER_PATH, ['apm', 'list', '--installed', pkgname], {
|
||||
shell: true,
|
||||
env: process.env
|
||||
});
|
||||
const child = spawn(
|
||||
SHELL_CALLER_PATH,
|
||||
["apm", "list", "--installed", pkgname],
|
||||
{
|
||||
shell: true,
|
||||
env: process.env,
|
||||
},
|
||||
);
|
||||
|
||||
let output = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
let output = "";
|
||||
|
||||
child.stdout.on("data", (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
child.on('close', (code) => {
|
||||
child.on("close", (code) => {
|
||||
if (code === 0 && output.includes(pkgname)) {
|
||||
isInstalled = true;
|
||||
logger.info(`应用已安装: ${pkgname}`);
|
||||
@@ -282,16 +295,16 @@ ipcMain.handle('check-installed', async (_event, pkgname: string) => {
|
||||
return isInstalled;
|
||||
});
|
||||
|
||||
ipcMain.on('remove-installed', async (_event, pkgname: string) => {
|
||||
ipcMain.on("remove-installed", async (_event, pkgname: string) => {
|
||||
const webContents = _event.sender;
|
||||
if (!pkgname) {
|
||||
logger.warn('remove-installed missing pkgname');
|
||||
logger.warn("remove-installed missing pkgname");
|
||||
return;
|
||||
}
|
||||
logger.info(`卸载已安装应用: ${pkgname}`);
|
||||
|
||||
|
||||
const superUserCmd = await checkSuperUserCommand();
|
||||
let execCommand = '';
|
||||
let execCommand = "";
|
||||
const execParams = [];
|
||||
if (superUserCmd.length > 0) {
|
||||
execCommand = superUserCmd;
|
||||
@@ -299,25 +312,29 @@ ipcMain.on('remove-installed', async (_event, pkgname: string) => {
|
||||
} else {
|
||||
execCommand = SHELL_CALLER_PATH;
|
||||
}
|
||||
const child = spawn(execCommand, [...execParams, 'apm', 'remove', '-y', pkgname], {
|
||||
shell: true,
|
||||
env: process.env
|
||||
});
|
||||
let output = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
const child = spawn(
|
||||
execCommand,
|
||||
[...execParams, "apm", "remove", "-y", pkgname],
|
||||
{
|
||||
shell: true,
|
||||
env: process.env,
|
||||
},
|
||||
);
|
||||
let output = "";
|
||||
|
||||
child.stdout.on("data", (data) => {
|
||||
const chunk = data.toString();
|
||||
output += chunk;
|
||||
webContents.send('remove-progress', chunk);
|
||||
webContents.send("remove-progress", chunk);
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
child.on("close", (code) => {
|
||||
const success = code === 0;
|
||||
// 拼接json消息
|
||||
const messageJSONObj = {
|
||||
message: success ? '卸载完成' : `卸载失败,退出码 ${code}`,
|
||||
message: success ? "卸载完成" : `卸载失败,退出码 ${code}`,
|
||||
stdout: output,
|
||||
stderr: ''
|
||||
stderr: "",
|
||||
};
|
||||
|
||||
if (success) {
|
||||
@@ -326,26 +343,28 @@ ipcMain.on('remove-installed', async (_event, pkgname: string) => {
|
||||
logger.error(messageJSONObj);
|
||||
}
|
||||
|
||||
webContents.send('remove-complete', {
|
||||
webContents.send("remove-complete", {
|
||||
id: 0,
|
||||
success: success,
|
||||
time: Date.now(),
|
||||
exitCode: code,
|
||||
message: JSON.stringify(messageJSONObj)
|
||||
message: JSON.stringify(messageJSONObj),
|
||||
} satisfies ChannelPayload);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('list-upgradable', async () => {
|
||||
const { code, stdout, stderr } = await runCommandCapture(
|
||||
SHELL_CALLER_PATH,
|
||||
['apm', 'list', '--upgradable']);
|
||||
ipcMain.handle("list-upgradable", async () => {
|
||||
const { code, stdout, stderr } = await runCommandCapture(SHELL_CALLER_PATH, [
|
||||
"apm",
|
||||
"list",
|
||||
"--upgradable",
|
||||
]);
|
||||
if (code !== 0) {
|
||||
logger.error(`list-upgradable failed: ${stderr || stdout}`);
|
||||
return {
|
||||
success: false,
|
||||
message: stderr || stdout || `list-upgradable failed with code ${code}`,
|
||||
apps: []
|
||||
apps: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -353,20 +372,25 @@ ipcMain.handle('list-upgradable', async () => {
|
||||
return { success: true, apps };
|
||||
});
|
||||
|
||||
ipcMain.handle('list-installed', async () => {
|
||||
ipcMain.handle("list-installed", async () => {
|
||||
const superUserCmd = await checkSuperUserCommand();
|
||||
const execCommand = superUserCmd.length > 0 ? superUserCmd : SHELL_CALLER_PATH;
|
||||
const execParams = superUserCmd.length > 0
|
||||
? [SHELL_CALLER_PATH, 'apm', 'list', '--installed']
|
||||
: ['apm', 'list', '--installed'];
|
||||
const execCommand =
|
||||
superUserCmd.length > 0 ? superUserCmd : SHELL_CALLER_PATH;
|
||||
const execParams =
|
||||
superUserCmd.length > 0
|
||||
? [SHELL_CALLER_PATH, "apm", "list", "--installed"]
|
||||
: ["apm", "list", "--installed"];
|
||||
|
||||
const { code, stdout, stderr } = await runCommandCapture(execCommand, execParams);
|
||||
const { code, stdout, stderr } = await runCommandCapture(
|
||||
execCommand,
|
||||
execParams,
|
||||
);
|
||||
if (code !== 0) {
|
||||
logger.error(`list-installed failed: ${stderr || stdout}`);
|
||||
return {
|
||||
success: false,
|
||||
message: stderr || stdout || `list-installed failed with code ${code}`,
|
||||
apps: []
|
||||
apps: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -374,19 +398,24 @@ ipcMain.handle('list-installed', async () => {
|
||||
return { success: true, apps };
|
||||
});
|
||||
|
||||
ipcMain.handle('uninstall-installed', async (_event, pkgname: string) => {
|
||||
ipcMain.handle("uninstall-installed", async (_event, pkgname: string) => {
|
||||
if (!pkgname) {
|
||||
logger.warn('uninstall-installed missing pkgname');
|
||||
return { success: false, message: 'missing pkgname' };
|
||||
logger.warn("uninstall-installed missing pkgname");
|
||||
return { success: false, message: "missing pkgname" };
|
||||
}
|
||||
|
||||
const superUserCmd = await checkSuperUserCommand();
|
||||
const execCommand = superUserCmd.length > 0 ? superUserCmd : SHELL_CALLER_PATH;
|
||||
const execParams = superUserCmd.length > 0
|
||||
? [SHELL_CALLER_PATH, 'apm', 'remove', '-y', pkgname]
|
||||
: ['apm', 'remove', '-y', pkgname];
|
||||
const execCommand =
|
||||
superUserCmd.length > 0 ? superUserCmd : SHELL_CALLER_PATH;
|
||||
const execParams =
|
||||
superUserCmd.length > 0
|
||||
? [SHELL_CALLER_PATH, "apm", "remove", "-y", pkgname]
|
||||
: ["apm", "remove", "-y", pkgname];
|
||||
|
||||
const { code, stdout, stderr } = await runCommandCapture(execCommand, execParams);
|
||||
const { code, stdout, stderr } = await runCommandCapture(
|
||||
execCommand,
|
||||
execParams,
|
||||
);
|
||||
const success = code === 0;
|
||||
|
||||
if (success) {
|
||||
@@ -397,24 +426,28 @@ ipcMain.handle('uninstall-installed', async (_event, pkgname: string) => {
|
||||
|
||||
return {
|
||||
success,
|
||||
message: success ? '卸载完成' : (stderr || stdout || `卸载失败,退出码 ${code}`)
|
||||
message: success
|
||||
? "卸载完成"
|
||||
: stderr || stdout || `卸载失败,退出码 ${code}`,
|
||||
};
|
||||
});
|
||||
|
||||
ipcMain.handle('launch-app', async (_event, pkgname: string) => {
|
||||
ipcMain.handle("launch-app", async (_event, pkgname: string) => {
|
||||
if (!pkgname) {
|
||||
logger.warn('No pkgname provided for launch-app');
|
||||
logger.warn("No pkgname provided for launch-app");
|
||||
}
|
||||
|
||||
const execCommand = "/opt/apm-store/extras/host-spawn";
|
||||
const execParams = ['/opt/apm-store/extras/apm-launcher', 'launch', pkgname ];
|
||||
const execParams = ["/opt/apm-store/extras/apm-launcher", "launch", pkgname];
|
||||
|
||||
logger.info(`Launching app: ${pkgname} with command: ${execCommand} ${execParams.join(' ')}`);
|
||||
logger.info(
|
||||
`Launching app: ${pkgname} with command: ${execCommand} ${execParams.join(" ")}`,
|
||||
);
|
||||
|
||||
spawn(execCommand, execParams, {
|
||||
shell: false,
|
||||
env: process.env,
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
stdio: "ignore",
|
||||
}).unref();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { app } from "electron";
|
||||
import pino from "pino";
|
||||
|
||||
const logger = pino({ 'name': 'deeplink.ts' });
|
||||
const logger = pino({ name: "deeplink.ts" });
|
||||
type Query = Record<string, string>;
|
||||
export type Listener = (query: Query) => void;
|
||||
|
||||
@@ -52,13 +52,13 @@ export const deepLink = {
|
||||
on: (event: string, listener: Listener) => {
|
||||
const count = listeners.add(event, listener);
|
||||
logger.info(
|
||||
`Deep link: listener added for event ${event}. Total event listeners: ${count}`
|
||||
`Deep link: listener added for event ${event}. Total event listeners: ${count}`,
|
||||
);
|
||||
},
|
||||
off: (event: string, listener: Listener) => {
|
||||
const count = listeners.remove(event, listener);
|
||||
logger.info(
|
||||
`Deep link: listener removed for event ${event}. Total event listeners: ${count}`
|
||||
`Deep link: listener removed for event ${event}. Total event listeners: ${count}`,
|
||||
);
|
||||
},
|
||||
once: (event: string, listener: Listener) => {
|
||||
@@ -72,7 +72,7 @@ export const deepLink = {
|
||||
|
||||
export function handleCommandLine(commandLine: string[]) {
|
||||
const target = commandLine.find((arg) =>
|
||||
protocols.some((protocol) => arg.startsWith(protocol + "://"))
|
||||
protocols.some((protocol) => arg.startsWith(protocol + "://")),
|
||||
);
|
||||
if (!target) return;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { deepLink } from './deeplink';
|
||||
import { isLoaded } from '../global';
|
||||
import pino from 'pino';
|
||||
import { BrowserWindow } from "electron";
|
||||
import { deepLink } from "./deeplink";
|
||||
import { isLoaded } from "../global";
|
||||
import pino from "pino";
|
||||
|
||||
const logger = pino({ 'name': 'handle-url-scheme.ts' });
|
||||
const logger = pino({ name: "handle-url-scheme.ts" });
|
||||
|
||||
const pendingActions: Array<() => void> = [];
|
||||
|
||||
@@ -24,22 +24,26 @@ new Promise<void>((resolve) => {
|
||||
});
|
||||
|
||||
deepLink.on("event", (query) => {
|
||||
logger.info(`Deep link: event "event" fired with query: ${JSON.stringify(query)}`);
|
||||
logger.info(
|
||||
`Deep link: event "event" fired with query: ${JSON.stringify(query)}`,
|
||||
);
|
||||
});
|
||||
|
||||
deepLink.on("action", (query) => {
|
||||
logger.info(`Deep link: event "action" fired with query: ${JSON.stringify(query)}`);
|
||||
|
||||
logger.info(
|
||||
`Deep link: event "action" fired with query: ${JSON.stringify(query)}`,
|
||||
);
|
||||
|
||||
const action = () => {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
if (!win) return;
|
||||
|
||||
if (query.cmd === 'update') {
|
||||
win.webContents.send('deep-link-update');
|
||||
if (query.cmd === "update") {
|
||||
win.webContents.send("deep-link-update");
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
} else if (query.cmd === 'list') {
|
||||
win.webContents.send('deep-link-installed');
|
||||
} else if (query.cmd === "list") {
|
||||
win.webContents.send("deep-link-installed");
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
}
|
||||
@@ -55,14 +59,16 @@ deepLink.on("action", (query) => {
|
||||
});
|
||||
|
||||
deepLink.on("install", (query) => {
|
||||
logger.info(`Deep link: event "install" fired with query: ${JSON.stringify(query)}`);
|
||||
|
||||
logger.info(
|
||||
`Deep link: event "install" fired with query: ${JSON.stringify(query)}`,
|
||||
);
|
||||
|
||||
const action = () => {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
if (!win) return;
|
||||
|
||||
if (query.pkg) {
|
||||
win.webContents.send('deep-link-install', query.pkg);
|
||||
win.webContents.send("deep-link-install", query.pkg);
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import { app, BrowserWindow, ipcMain, Menu, shell, Tray } from 'electron'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import fs from 'node:fs'
|
||||
import pino from 'pino'
|
||||
import { handleCommandLine } from './deeplink.js'
|
||||
import { isLoaded } from '../global.js'
|
||||
import { tasks } from './backend/install-manager.js'
|
||||
|
||||
import { app, BrowserWindow, ipcMain, Menu, shell, Tray } from "electron";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import fs from "node:fs";
|
||||
import pino from "pino";
|
||||
import { handleCommandLine } from "./deeplink.js";
|
||||
import { isLoaded } from "../global.js";
|
||||
import { tasks } from "./backend/install-manager.js";
|
||||
|
||||
// Assure single instance application
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.exit(0);
|
||||
}
|
||||
|
||||
import './backend/install-manager.js'
|
||||
import './handle-url-scheme.js'
|
||||
import "./backend/install-manager.js";
|
||||
import "./handle-url-scheme.js";
|
||||
|
||||
const logger = pino({ 'name': 'index.ts' });
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const logger = pino({ name: "index.ts" });
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// The built directory structure
|
||||
//
|
||||
@@ -30,38 +29,38 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
// ├─┬ dist
|
||||
// │ └── index.html > Electron-Renderer
|
||||
//
|
||||
process.env.APP_ROOT = path.join(__dirname, '../..')
|
||||
process.env.APP_ROOT = path.join(__dirname, "../..");
|
||||
|
||||
export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron')
|
||||
export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist')
|
||||
export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL
|
||||
export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
|
||||
export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
|
||||
export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;
|
||||
|
||||
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
|
||||
? path.join(process.env.APP_ROOT, 'public')
|
||||
: RENDERER_DIST
|
||||
? path.join(process.env.APP_ROOT, "public")
|
||||
: RENDERER_DIST;
|
||||
|
||||
// Disable GPU Acceleration for Windows 7
|
||||
if (os.release().startsWith('6.1')) app.disableHardwareAcceleration()
|
||||
if (os.release().startsWith("6.1")) app.disableHardwareAcceleration();
|
||||
|
||||
// Set application name for Windows 10+ notifications
|
||||
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
|
||||
if (process.platform === "win32") app.setAppUserModelId(app.getName());
|
||||
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
process.exit(0)
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let win: BrowserWindow | null = null
|
||||
const preload = path.join(__dirname, '../preload/index.mjs')
|
||||
const indexHtml = path.join(RENDERER_DIST, 'index.html')
|
||||
let win: BrowserWindow | null = null;
|
||||
const preload = path.join(__dirname, "../preload/index.mjs");
|
||||
const indexHtml = path.join(RENDERER_DIST, "index.html");
|
||||
|
||||
async function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
title: 'APM AppStore',
|
||||
title: "APM AppStore",
|
||||
width: 1366,
|
||||
height: 768,
|
||||
autoHideMenuBar: true,
|
||||
icon: path.join(process.env.VITE_PUBLIC, 'favicon.ico'),
|
||||
icon: path.join(process.env.VITE_PUBLIC, "favicon.ico"),
|
||||
webPreferences: {
|
||||
preload,
|
||||
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
|
||||
@@ -71,30 +70,31 @@ async function createWindow() {
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
|
||||
// contextIsolation: false,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
if (VITE_DEV_SERVER_URL) { // #298
|
||||
win.loadURL(VITE_DEV_SERVER_URL)
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
// #298
|
||||
win.loadURL(VITE_DEV_SERVER_URL);
|
||||
// Open devTool if the app is not packaged
|
||||
win.webContents.openDevTools({mode:'detach'})
|
||||
win.webContents.openDevTools({ mode: "detach" });
|
||||
} else {
|
||||
win.loadFile(indexHtml)
|
||||
win.loadFile(indexHtml);
|
||||
}
|
||||
|
||||
// Test actively push message to the Electron-Renderer
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
win?.webContents.send('main-process-message', new Date().toLocaleString())
|
||||
logger.info('Renderer process is ready.');
|
||||
})
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win?.webContents.send("main-process-message", new Date().toLocaleString());
|
||||
logger.info("Renderer process is ready.");
|
||||
});
|
||||
|
||||
// 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' }
|
||||
})
|
||||
if (url.startsWith("https:")) shell.openExternal(url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
||||
|
||||
win.on('close', (event) => {
|
||||
win.on("close", (event) => {
|
||||
// 截获 close 默认行为
|
||||
event.preventDefault();
|
||||
// 点击关闭时触发close事件,我们按照之前的思路在关闭时,隐藏窗口,隐藏任务栏窗口
|
||||
@@ -103,50 +103,52 @@ async function createWindow() {
|
||||
win.setSkipTaskbar(true);
|
||||
} else {
|
||||
// 如果没有下载任务,才允许关闭窗口
|
||||
win.destroy()
|
||||
win.destroy();
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
ipcMain.on('renderer-ready', (event, args) => {
|
||||
logger.info('Received renderer-ready event with args: ' + JSON.stringify(args));
|
||||
ipcMain.on("renderer-ready", (event, args) => {
|
||||
logger.info(
|
||||
"Received renderer-ready event with args: " + JSON.stringify(args),
|
||||
);
|
||||
isLoaded.value = args.status;
|
||||
logger.info(`isLoaded set to: ${isLoaded.value}`);
|
||||
})
|
||||
});
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
handleCommandLine(process.argv)
|
||||
})
|
||||
createWindow();
|
||||
handleCommandLine(process.argv);
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
win = null
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
app.on("window-all-closed", () => {
|
||||
win = null;
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
app.on('second-instance', () => {
|
||||
app.on("second-instance", () => {
|
||||
if (win) {
|
||||
// Focus on the main window if the user tried to open another
|
||||
if (win.isMinimized()) win.restore()
|
||||
win.focus()
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
const allWindows = BrowserWindow.getAllWindows()
|
||||
app.on("activate", () => {
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
if (allWindows.length) {
|
||||
allWindows[0].focus()
|
||||
allWindows[0].focus();
|
||||
} else {
|
||||
createWindow()
|
||||
createWindow();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 设置托盘
|
||||
// 获取图标路径
|
||||
function getIconPath() {
|
||||
let iconPath = "";
|
||||
const iconFile = process.platform === "win32" ? "amber-pm-logo.ico" : "amber-pm-logo.png"; // 图标文件名,linux下需要png格式,不然会不显示
|
||||
const iconFile =
|
||||
process.platform === "win32" ? "amber-pm-logo.ico" : "amber-pm-logo.png"; // 图标文件名,linux下需要png格式,不然会不显示
|
||||
// 判断是否在打包模式
|
||||
if (app.isPackaged) {
|
||||
// 打包模式
|
||||
@@ -170,31 +172,35 @@ function getIconPath() {
|
||||
|
||||
let tray = null;
|
||||
app.whenReady().then(() => {
|
||||
tray = new Tray(getIconPath());
|
||||
const contextMenu = Menu.buildFromTemplate([{
|
||||
label: '显示主界面',
|
||||
click: () => { win.show() }
|
||||
tray = new Tray(getIconPath());
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: "显示主界面",
|
||||
click: () => {
|
||||
win.show();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '退出程序',
|
||||
click: () => { win.destroy() }
|
||||
label: "退出程序",
|
||||
click: () => {
|
||||
win.destroy();
|
||||
},
|
||||
},
|
||||
]);
|
||||
tray.setToolTip("APM 应用商店");
|
||||
tray.setContextMenu(contextMenu);
|
||||
// 双击触发
|
||||
tray.on("click", () => {
|
||||
// 双击通知区图标实现应用的显示或隐藏
|
||||
if (win.isVisible()) {
|
||||
win.hide();
|
||||
win.setSkipTaskbar(true);
|
||||
} else {
|
||||
win.show();
|
||||
win.setSkipTaskbar(false);
|
||||
}
|
||||
]);
|
||||
tray.setToolTip('APM 应用商店')
|
||||
tray.setContextMenu(contextMenu)
|
||||
// 双击触发
|
||||
tray.on('click', () => {
|
||||
// 双击通知区图标实现应用的显示或隐藏
|
||||
if (win.isVisible()) {
|
||||
win.hide();
|
||||
win.setSkipTaskbar(true);
|
||||
} else {
|
||||
win.show();
|
||||
win.setSkipTaskbar(false);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// New window example arg: new windows url
|
||||
// ipcMain.handle('open-win', (_, arg) => {
|
||||
|
||||
Reference in New Issue
Block a user