Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 44 additions & 24 deletions src/main/analystic-node.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
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

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--;
}
}
61 changes: 55 additions & 6 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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();
}
})

Expand All @@ -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

Expand Down Expand Up @@ -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) => {
Expand Down