feat: support download statistics

close #15
This commit is contained in:
Elysia
2026-02-14 23:11:43 +08:00
parent c4ffc880e4
commit 5ac9376200
9 changed files with 99 additions and 45 deletions

View File

@@ -1,2 +1,3 @@
VITE_APM_STORE_LOCAL_MODE=true VITE_APM_STORE_LOCAL_MODE=true
VITE_APM_STORE_BASE_URL=/local_amd64-apm VITE_APM_STORE_BASE_URL=/local_amd64-apm
VITE_APM_STORE_STATS_BASE_URL=/local_stats

View File

@@ -1 +1,2 @@
VITE_APM_STORE_BASE_URL=https://erotica.spark-app.store VITE_APM_STORE_BASE_URL=https://erotica.spark-app.store
VITE_APM_STORE_STATS_BASE_URL=https://feedback.spark-app.store

6
.vscode/launch.json vendored
View File

@@ -25,9 +25,9 @@
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": { // "windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" // "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
}, // },
"runtimeArgs": [ "runtimeArgs": [
"--remote-debugging-port=9229", "--remote-debugging-port=9229",
"." "."

View File

@@ -6,6 +6,7 @@ import {
shell, shell,
Tray, Tray,
nativeTheme, nativeTheme,
session
} from "electron"; } from "electron";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import path from "node:path"; import path from "node:path";
@@ -61,6 +62,7 @@ if (!app.requestSingleInstanceLock()) {
let win: BrowserWindow | null = null; let win: BrowserWindow | null = null;
const preload = path.join(__dirname, "../preload/index.mjs"); const preload = path.join(__dirname, "../preload/index.mjs");
const indexHtml = path.join(RENDERER_DIST, "index.html"); const indexHtml = path.join(RENDERER_DIST, "index.html");
const userAgent = `APM-Store/${JSON.stringify(process.env.npm_package_version)}`
async function createWindow() { async function createWindow() {
win = new BrowserWindow({ win = new BrowserWindow({
@@ -129,6 +131,11 @@ ipcMain.on("set-theme-source", (event, theme: "system" | "light" | "dark") => {
}); });
app.whenReady().then(() => { app.whenReady().then(() => {
// Set User-Agent for client
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
details.requestHeaders["User-Agent"] = userAgent;
callback({ cancel: false, requestHeaders: details.requestHeaders });
});
createWindow(); createWindow();
handleCommandLine(process.argv); handleCommandLine(process.argv);
}); });
@@ -159,7 +166,7 @@ app.on("will-quit", () => {
// Clean up temp dir // Clean up temp dir
logger.info("Cleaning up temp dir"); logger.info("Cleaning up temp dir");
fs.rmSync("/tmp/apm-store/", { recursive: true, force: true }); fs.rmSync("/tmp/apm-store/", { recursive: true, force: true });
logger.info("Done, exiting") logger.info("Done, exiting");
}); });
// 设置托盘 // 设置托盘

View File

@@ -4,6 +4,9 @@ import type { App } from "./typedefinition";
export const APM_STORE_BASE_URL: string = export const APM_STORE_BASE_URL: string =
import.meta.env.VITE_APM_STORE_BASE_URL || ""; import.meta.env.VITE_APM_STORE_BASE_URL || "";
export const APM_STORE_STATS_BASE_URL: string =
import.meta.env.VITE_APM_STORE_STATS_BASE_URL || "";
// 下面的变量用于存储当前应用的信息,其实用在多个组件中 // 下面的变量用于存储当前应用的信息,其实用在多个组件中
export const currentApp = ref<App | null>(null); export const currentApp = ref<App | null>(null);
export const currentAppIsInstalled = ref(false); export const currentAppIsInstalled = ref(false);

View File

@@ -3,7 +3,11 @@
// }) // })
import pino from "pino"; import pino from "pino";
import { currentApp, currentAppIsInstalled } from "../global/storeConfig"; import {
APM_STORE_STATS_BASE_URL,
currentApp,
currentAppIsInstalled,
} from "../global/storeConfig";
import { APM_STORE_BASE_URL } from "../global/storeConfig"; import { APM_STORE_BASE_URL } from "../global/storeConfig";
import { downloads } from "../global/downloadStatus"; import { downloads } from "../global/downloadStatus";
@@ -14,6 +18,7 @@ import {
App, App,
DownloadItemStatus, DownloadItemStatus,
} from "../global/typedefinition"; } from "../global/typedefinition";
import axios from "axios";
let downloadIdCounter = 0; let downloadIdCounter = 0;
const logger = pino({ name: "processInstall.ts" }); const logger = pino({ name: "processInstall.ts" });
@@ -53,10 +58,27 @@ export const handleInstall = () => {
// Send to main process to start download // Send to main process to start download
window.ipcRenderer.send("queue-install", JSON.stringify(download)); window.ipcRenderer.send("queue-install", JSON.stringify(download));
// const encodedPkg = encodeURIComponent(currentApp.value.Pkgname); // Send download statistics to server
// openApmStoreUrl(`apmstore://install?pkg=${encodedPkg}`, { logger.info("发送下载次数统计...");
// fallbackText: `/usr/bin/apm-installer --install ${currentApp.value.Pkgname}` const axiosInstance = axios.create({
// }); baseURL: APM_STORE_STATS_BASE_URL,
timeout: 5000, // 增加到 5 秒,避免网络波动导致的超时
});
axiosInstance
.post(
"/handle_post",
{
path: `${window.apm_store.arch}/${currentApp.value.category}/${currentApp.value.pkgname}`,
},
{
headers: {
"Content-Type": "application/json",
},
}
)
.then((response) => {
logger.info("下载次数统计已发送,状态:", response.data);
});
}; };
export const handleRetry = (download_: DownloadItem) => { export const handleRetry = (download_: DownloadItem) => {
@@ -124,7 +146,7 @@ window.ipcRenderer.on(
if (downloadObj) { if (downloadObj) {
downloadObj.progress = payload.progress; downloadObj.progress = payload.progress;
} }
}, }
); );
window.ipcRenderer.on("install-log", (_event, log: InstallLog) => { window.ipcRenderer.on("install-log", (_event, log: InstallLog) => {

2
src/vite-env.d.ts vendored
View File

@@ -12,3 +12,5 @@ interface Window {
ipcRenderer: import("electron").IpcRenderer; ipcRenderer: import("electron").IpcRenderer;
apm_store: any; apm_store: any;
} }
declare const __APP_VERSION__: string;

View File

@@ -7,11 +7,11 @@ import pkg from './package.json'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ command }) => { export default defineConfig(({ command }) => {
fs.rmSync('dist-electron', { recursive: true, force: true }) fs.rmSync("dist-electron", { recursive: true, force: true });
const isServe = command === 'serve' const isServe = command === "serve";
const isBuild = command === 'build' const isBuild = command === "build";
const sourcemap = isServe || !!process.env.VSCODE_DEBUG const sourcemap = isServe || !!process.env.VSCODE_DEBUG;
return { return {
plugins: [ plugins: [
@@ -19,25 +19,29 @@ export default defineConfig(({ command }) => {
electron({ electron({
main: { main: {
// Shortcut of `build.lib.entry` // Shortcut of `build.lib.entry`
entry: 'electron/main/index.ts', entry: "electron/main/index.ts",
onstart({ startup }) { onstart({ startup }) {
if (process.env.VSCODE_DEBUG) { if (process.env.VSCODE_DEBUG) {
console.log(/* For `.vscode/.debug.script.mjs` */'[startup] Electron App') console.log(
/* For `.vscode/.debug.script.mjs` */ "[startup] Electron App"
);
} else { } else {
startup() startup();
} }
}, },
vite: { vite: {
build: { build: {
sourcemap, sourcemap,
minify: isBuild, minify: isBuild,
outDir: 'dist-electron/main', outDir: "dist-electron/main",
rollupOptions: { rollupOptions: {
// Some third-party Node.js libraries may not be built correctly by Vite, especially `C/C++` addons, // Some third-party Node.js libraries may not be built correctly by Vite, especially `C/C++` addons,
// we can use `external` to exclude them to ensure they work correctly. // we can use `external` to exclude them to ensure they work correctly.
// Others need to put them in `dependencies` to ensure they are collected into `app.asar` after the app is built. // Others need to put them in `dependencies` to ensure they are collected into `app.asar` after the app is built.
// Of course, this is not absolute, just this way is relatively simple. :) // Of course, this is not absolute, just this way is relatively simple. :)
external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}), external: Object.keys(
"dependencies" in pkg ? pkg.dependencies : {}
),
}, },
}, },
}, },
@@ -45,14 +49,16 @@ export default defineConfig(({ command }) => {
preload: { preload: {
// Shortcut of `build.rollupOptions.input`. // Shortcut of `build.rollupOptions.input`.
// Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`. // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`.
input: 'electron/preload/index.ts', input: "electron/preload/index.ts",
vite: { vite: {
build: { build: {
sourcemap: sourcemap ? 'inline' : undefined, // #332 sourcemap: sourcemap ? "inline" : undefined, // #332
minify: isBuild, minify: isBuild,
outDir: 'dist-electron/preload', outDir: "dist-electron/preload",
rollupOptions: { rollupOptions: {
external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}), external: Object.keys(
"dependencies" in pkg ? pkg.dependencies : {}
),
}, },
}, },
}, },
@@ -71,26 +77,38 @@ export default defineConfig(({ command }) => {
host: url.hostname, host: url.hostname,
port: +url.port, port: +url.port,
proxy: { proxy: {
'/local_amd64-apm': { "/local_amd64-apm": {
target: 'https://erotica.spark-app.store', target: "https://erotica.spark-app.store",
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/local_amd64-apm/, ''), rewrite: (path) => path.replace(/^\/local_amd64-apm/, ""),
} },
} "/local_stats": {
} target: "https://feedback.spark-app.store",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/local_stats/, ""),
},
},
};
} else { } else {
return { return {
proxy: { proxy: {
'/local_amd64-apm': { "/local_amd64-apm": {
target: 'https://erotica.spark-app.store', target: "https://erotica.spark-app.store",
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/local_amd64-apm/, ''), rewrite: (path) => path.replace(/^\/local_amd64-apm/, ""),
} },
} "/local_stats": {
} target: "https://feedback.spark-app.store",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/local_stats/, ""),
},
},
};
} }
} })(),
)(),
clearScreen: false, clearScreen: false,
} define: {
}) __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
},
};
});