diff --git a/.env.debug b/.env.debug index 430fb433..0742a13a 100644 --- a/.env.debug +++ b/.env.debug @@ -1,2 +1,3 @@ VITE_APM_STORE_LOCAL_MODE=true -VITE_APM_STORE_BASE_URL=/local_amd64-apm \ No newline at end of file +VITE_APM_STORE_BASE_URL=/local_amd64-apm +VITE_APM_STORE_STATS_BASE_URL=/local_stats diff --git a/.env.production b/.env.production index 48bc2f89..67893ab1 100644 --- a/.env.production +++ b/.env.production @@ -1 +1,2 @@ -VITE_APM_STORE_BASE_URL=https://erotica.spark-app.store \ No newline at end of file +VITE_APM_STORE_BASE_URL=https://erotica.spark-app.store +VITE_APM_STORE_STATS_BASE_URL=https://feedback.spark-app.store diff --git a/.vscode/launch.json b/.vscode/launch.json index 3d01d65a..a02acbfe 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,9 +25,9 @@ "type": "node", "request": "launch", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", - "windows": { - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" - }, + // "windows": { + // "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + // }, "runtimeArgs": [ "--remote-debugging-port=9229", "." diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index 6834a2ce..e4d30430 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -18,8 +18,8 @@ type InstallTask = { download_process: ChildProcess | null; install_process: ChildProcess | null; webContents: WebContents | null; - downloadDir?: string; - metalinkUrl?: string; + downloadDir?: string; + metalinkUrl?: string; filename?: string; }; diff --git a/electron/main/index.ts b/electron/main/index.ts index ce236ff0..82be9541 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -6,6 +6,7 @@ import { shell, Tray, nativeTheme, + session } from "electron"; import { fileURLToPath } from "node:url"; import path from "node:path"; @@ -61,6 +62,7 @@ if (!app.requestSingleInstanceLock()) { let win: BrowserWindow | null = null; const preload = path.join(__dirname, "../preload/index.mjs"); const indexHtml = path.join(RENDERER_DIST, "index.html"); +const userAgent = `APM-Store/${JSON.stringify(process.env.npm_package_version)}` async function createWindow() { win = new BrowserWindow({ @@ -129,6 +131,11 @@ ipcMain.on("set-theme-source", (event, theme: "system" | "light" | "dark") => { }); 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(); handleCommandLine(process.argv); }); @@ -159,7 +166,7 @@ app.on("will-quit", () => { // Clean up temp dir logger.info("Cleaning up temp dir"); fs.rmSync("/tmp/apm-store/", { recursive: true, force: true }); - logger.info("Done, exiting") + logger.info("Done, exiting"); }); // 设置托盘 diff --git a/src/global/storeConfig.ts b/src/global/storeConfig.ts index 266de7fd..04888608 100644 --- a/src/global/storeConfig.ts +++ b/src/global/storeConfig.ts @@ -4,6 +4,9 @@ import type { App } from "./typedefinition"; export const APM_STORE_BASE_URL: string = 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(null); export const currentAppIsInstalled = ref(false); diff --git a/src/modules/processInstall.ts b/src/modules/processInstall.ts index 5eb3411d..25a80e34 100644 --- a/src/modules/processInstall.ts +++ b/src/modules/processInstall.ts @@ -3,7 +3,11 @@ // }) 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 { downloads } from "../global/downloadStatus"; @@ -14,6 +18,7 @@ import { App, DownloadItemStatus, } from "../global/typedefinition"; +import axios from "axios"; let downloadIdCounter = 0; const logger = pino({ name: "processInstall.ts" }); @@ -53,10 +58,27 @@ export const handleInstall = () => { // Send to main process to start download window.ipcRenderer.send("queue-install", JSON.stringify(download)); - // const encodedPkg = encodeURIComponent(currentApp.value.Pkgname); - // openApmStoreUrl(`apmstore://install?pkg=${encodedPkg}`, { - // fallbackText: `/usr/bin/apm-installer --install ${currentApp.value.Pkgname}` - // }); + // Send download statistics to server + logger.info("发送下载次数统计..."); + 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) => { @@ -124,7 +146,7 @@ window.ipcRenderer.on( if (downloadObj) { downloadObj.progress = payload.progress; } - }, + } ); window.ipcRenderer.on("install-log", (_event, log: InstallLog) => { diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index b76a4f39..20f21bc6 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -12,3 +12,5 @@ interface Window { ipcRenderer: import("electron").IpcRenderer; apm_store: any; } + +declare const __APP_VERSION__: string; diff --git a/vite.config.ts b/vite.config.ts index be3cadee..21741c3d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,11 +7,11 @@ import pkg from './package.json' // https://vitejs.dev/config/ 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 isBuild = command === 'build' - const sourcemap = isServe || !!process.env.VSCODE_DEBUG + const isServe = command === "serve"; + const isBuild = command === "build"; + const sourcemap = isServe || !!process.env.VSCODE_DEBUG; return { plugins: [ @@ -19,25 +19,29 @@ export default defineConfig(({ command }) => { electron({ main: { // Shortcut of `build.lib.entry` - entry: 'electron/main/index.ts', + entry: "electron/main/index.ts", onstart({ startup }) { 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 { - startup() + startup(); } }, vite: { build: { sourcemap, minify: isBuild, - outDir: 'dist-electron/main', + outDir: "dist-electron/main", 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. // 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. :) - 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: { // Shortcut of `build.rollupOptions.input`. // 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: { build: { - sourcemap: sourcemap ? 'inline' : undefined, // #332 + sourcemap: sourcemap ? "inline" : undefined, // #332 minify: isBuild, - outDir: 'dist-electron/preload', + outDir: "dist-electron/preload", 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, port: +url.port, proxy: { - '/local_amd64-apm': { - target: 'https://erotica.spark-app.store', + "/local_amd64-apm": { + target: "https://erotica.spark-app.store", 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 { return { proxy: { - '/local_amd64-apm': { - target: 'https://erotica.spark-app.store', + "/local_amd64-apm": { + target: "https://erotica.spark-app.store", 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), + }, + }; +});