diff --git a/src/main/analystic-node.ts b/src/main/analystic-node.ts index d6681a722..4959587c5 100644 --- a/src/main/analystic-node.ts +++ b/src/main/analystic-node.ts @@ -1,6 +1,7 @@ import * as store from './store-node' import { app } from 'electron' import { ofetch } from 'ofetch' +import log from 'electron-log' // https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?hl=zh-cn&client_type=gtag // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag&hl=zh-cn#required_parameters @@ -8,30 +9,49 @@ import { ofetch } from 'ofetch' const measurement_id = `G-T6Q7MNPNLK` const api_secret = `pRnsvLo-REWLVzV_PbKvWg` +// Track pending requests to prevent EPIPE errors during shutdown +export let pendingRequests = 0 + export async function event(name: string, params: any = {}) { - const clientId = store.getConfig().uuid - const res = await ofetch( - `https://www.google-analytics.com/mp/collect?measurement_id=${measurement_id}&api_secret=${api_secret}`, - { - method: 'POST', - body: { - user_id: clientId, - client_id: clientId, - events: [ - { - name: name, - params: { - app_name: 'chatbox', - app_version: app.getVersion(), - chatbox_platform_type: 'desktop', - chatbox_platform: 'desktop', - app_platform: process.platform, - ...params, + // Skip analytics if app is quitting to prevent EPIPE errors + if (app.isQuitting) { + return null; + } + + try { + pendingRequests++; + const clientId = store.getConfig().uuid; + const res = await ofetch( + `https://www.google-analytics.com/mp/collect?measurement_id=${measurement_id}&api_secret=${api_secret}`, + { + method: 'POST', + body: { + user_id: clientId, + client_id: clientId, + events: [ + { + name: name, + params: { + app_name: 'chatbox', + app_version: app.getVersion(), + chatbox_platform_type: 'desktop', + chatbox_platform: 'desktop', + app_platform: process.platform, + ...params, + }, }, - }, - ], - }, - } - ) - return res + ], + }, + // Add timeout to prevent hanging during shutdown + timeout: 3000, + } + ); + return res; + } catch (error) { + // Use log instead of console.log to avoid EPIPE errors + log.error('Analytics error:', error); + return null; + } finally { + pendingRequests--; + } } diff --git a/src/main/main.ts b/src/main/main.ts index 36893f164..5848a9c53 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -20,6 +20,14 @@ import { store, getConfig, getSettings } from './store-node' import * as proxy from './proxy' import * as fs from 'fs-extra' import * as analystic from './analystic-node' +// Add app.isQuitting property to the Electron.App interface +declare global { + namespace Electron { + interface App { + isQuitting?: boolean; + } + } +} import sanitizeFilename from 'sanitize-filename' if (process.platform === 'win32') { @@ -153,11 +161,39 @@ const createWindow = async () => { * Add event listeners... */ +// Function to safely quit the app after cleanup +const safeQuit = () => { + // Mark app as quitting to prevent new network requests + app.isQuitting = true; + + // Only quit when no pending analytics requests + if (analystic.pendingRequests === 0) { + app.quit(); + } else { + // Wait a bit and check again + setTimeout(() => safeQuit(), 100); + } +} + +// Handle the 'before-quit' event +app.on('before-quit', (event) => { + // If app is already in quitting state, don't prevent + if (app.isQuitting && analystic.pendingRequests === 0) { + return; + } + + // Prevent the default quit + event.preventDefault(); + + // Use our safe quit procedure instead + safeQuit(); +}); + app.on('window-all-closed', () => { // Respect the OSX convention of having the application in memory even // after all windows have been closed if (process.platform !== 'darwin') { - app.quit() + safeQuit(); } }) @@ -171,7 +207,10 @@ app.whenReady() }) proxy.init() }) - .catch(console.log) + .catch((error) => { + // Use electron-log instead of console.log to avoid EPIPE errors + log.error('App initialization error:', error) + }) // IPC @@ -226,10 +265,20 @@ ipcMain.handle('relaunch', () => { }) ipcMain.handle('analysticTrackingEvent', (event, dataJson) => { - const data = JSON.parse(dataJson) - analystic.event(data.name, data.params).catch((e) => { - log.error('analystic_tracking_event', e) - }) + // Skip analytics if app is quitting + if (app.isQuitting) { + return; + } + + try { + const data = JSON.parse(dataJson); + // Don't await this promise to avoid blocking + analystic.event(data.name, data.params).catch((e) => { + log.error('analystic_tracking_event', e); + }); + } catch (e) { + log.error('analystic_tracking_event_parse', e); + } }) ipcMain.handle('getConfig', (event) => {