mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-08 00:47:15 +08:00
add gtag
This commit is contained in:
@@ -38,7 +38,8 @@
|
|||||||
"@geekgeekrun/utils": "workspace:*",
|
"@geekgeekrun/utils": "workspace:*",
|
||||||
"JSONStream": "^1.3.5",
|
"JSONStream": "^1.3.5",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"electron-updater": "^6.1.7"
|
"electron-updater": "^6.1.7",
|
||||||
|
"node-machine-id": "^1.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-toolkit/eslint-config": "^1.0.2",
|
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { checkAndDownloadPuppeteerExecutable } from './utils/puppeteer-executabl
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { pipeWriteRegardlessError } from '../utils/pipe'
|
import { pipeWriteRegardlessError } from '../utils/pipe'
|
||||||
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
|
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
|
||||||
|
import gtag from '../../utils/gtag'
|
||||||
|
|
||||||
export enum DOWNLOAD_ERROR_EXIT_CODE {
|
export enum DOWNLOAD_ERROR_EXIT_CODE {
|
||||||
DOWNLOAD_ERROR = 80
|
DOWNLOAD_ERROR = 80
|
||||||
@@ -83,9 +84,11 @@ export const checkAndDownloadDependenciesForInit = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await promiseWithResolver.promise
|
await promiseWithResolver.promise
|
||||||
|
gtag('browser_download_finished')
|
||||||
app.exit()
|
app.exit()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
gtag('browser_download_error')
|
||||||
pipeWriteRegardlessError(
|
pipeWriteRegardlessError(
|
||||||
pipe,
|
pipe,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
+3
@@ -11,6 +11,7 @@ import {
|
|||||||
import { getExecutableFileVersion } from '@geekgeekrun/utils/windows-only/file.mjs'
|
import { getExecutableFileVersion } from '@geekgeekrun/utils/windows-only/file.mjs'
|
||||||
import createCheckAndLocateExistedChromiumExecutableWorker from './worker/find-and-locate-existed-chromium-executable?nodeWorker&url'
|
import createCheckAndLocateExistedChromiumExecutableWorker from './worker/find-and-locate-existed-chromium-executable?nodeWorker&url'
|
||||||
import { type Worker, isMainThread } from 'node:worker_threads'
|
import { type Worker, isMainThread } from 'node:worker_threads'
|
||||||
|
import gtag from '../../../../utils/gtag'
|
||||||
|
|
||||||
const getPuppeteerManagerModule = async () => {
|
const getPuppeteerManagerModule = async () => {
|
||||||
let puppeteerManager
|
let puppeteerManager
|
||||||
@@ -82,6 +83,7 @@ export const checkAndDownloadPuppeteerExecutable = async (
|
|||||||
const puppeteerManager = await getPuppeteerManagerModule()
|
const puppeteerManager = await getPuppeteerManagerModule()
|
||||||
let installedBrowser: InstalledBrowser
|
let installedBrowser: InstalledBrowser
|
||||||
if (!(await checkCachedPuppeteerExecutable())) {
|
if (!(await checkCachedPuppeteerExecutable())) {
|
||||||
|
gtag('need_download_browser')
|
||||||
try {
|
try {
|
||||||
await options.confirmContinuePromise
|
await options.confirmContinuePromise
|
||||||
} catch {
|
} catch {
|
||||||
@@ -100,6 +102,7 @@ export const checkAndDownloadPuppeteerExecutable = async (
|
|||||||
downloadProgressCallback: options.downloadProgressCallback
|
downloadProgressCallback: options.downloadProgressCallback
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
gtag('use_installed_browser')
|
||||||
installedBrowser = (
|
installedBrowser = (
|
||||||
await puppeteerManager.getInstalledBrowsers({
|
await puppeteerManager.getInstalledBrowsers({
|
||||||
cacheDir
|
cacheDir
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import fs, { WriteStream } from 'node:fs'
|
|||||||
import { pipeWriteRegardlessError } from '../utils/pipe'
|
import { pipeWriteRegardlessError } from '../utils/pipe'
|
||||||
import * as JSONStream from 'JSONStream'
|
import * as JSONStream from 'JSONStream'
|
||||||
import { initPowerSaveBlocker } from './power-saver-blocker'
|
import { initPowerSaveBlocker } from './power-saver-blocker'
|
||||||
|
import gtag from '../../utils/gtag'
|
||||||
|
|
||||||
const rerunInterval = (() => {
|
const rerunInterval = (() => {
|
||||||
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
|
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
|
||||||
@@ -115,4 +116,6 @@ export function runAutoChatWithDaemon() {
|
|||||||
type: 'AUTO_START_CHAT_DAEMON_PROCESS_STARTUP'
|
type: 'AUTO_START_CHAT_DAEMON_PROCESS_STARTUP'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gtag('run_auto_chat_with_boss_daemon_ready')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../common/enums/auto-start-chat
|
|||||||
import * as JSONStream from 'JSONStream'
|
import * as JSONStream from 'JSONStream'
|
||||||
|
|
||||||
import SqlitePluginModule from '@geekgeekrun/sqlite-plugin'
|
import SqlitePluginModule from '@geekgeekrun/sqlite-plugin'
|
||||||
|
import gtag from '../../utils/gtag'
|
||||||
const { default: SqlitePlugin } = SqlitePluginModule
|
const { default: SqlitePlugin } = SqlitePluginModule
|
||||||
|
|
||||||
const rerunInterval = (() => {
|
const rerunInterval = (() => {
|
||||||
@@ -94,6 +95,8 @@ const runAutoChat = async () => {
|
|||||||
}
|
}
|
||||||
initPlugins(hooks)
|
initPlugins(hooks)
|
||||||
await hooks.daemonInitialized.promise()
|
await hooks.daemonInitialized.promise()
|
||||||
|
|
||||||
|
gtag('run_auto_chat_with_boss_main_ready')
|
||||||
pipeWriteRegardlessError(
|
pipeWriteRegardlessError(
|
||||||
pipe,
|
pipe,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { Target } from 'puppeteer'
|
|||||||
import { pipeWriteRegardlessError } from '../utils/pipe'
|
import { pipeWriteRegardlessError } from '../utils/pipe'
|
||||||
import * as JSONStream from 'JSONStream'
|
import * as JSONStream from 'JSONStream'
|
||||||
import { ChatStartupFrom } from '@geekgeekrun/sqlite-plugin/dist/entity/ChatStartupLog'
|
import { ChatStartupFrom } from '@geekgeekrun/sqlite-plugin/dist/entity/ChatStartupLog'
|
||||||
|
import gtag from '../../utils/gtag'
|
||||||
|
|
||||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||||
const isRunFromUi = Boolean(process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE)
|
const isRunFromUi = Boolean(process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE)
|
||||||
@@ -225,7 +226,7 @@ export async function launchBossSite() {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
//#endregion
|
//#endregion
|
||||||
|
gtag('launch_boss_site_ready')
|
||||||
browser.on('targetcreated', (target) => {
|
browser.on('targetcreated', (target) => {
|
||||||
attachRequestsListener(target)
|
attachRequestsListener(target)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { electronApp, optimizer } from '@electron-toolkit/utils'
|
|||||||
import { createMainWindow } from '../../window/mainWindow'
|
import { createMainWindow } from '../../window/mainWindow'
|
||||||
import './app-menu'
|
import './app-menu'
|
||||||
import initIpc from './ipc'
|
import initIpc from './ipc'
|
||||||
|
import gtag from '../../utils/gtag'
|
||||||
export function openSettingWindow() {
|
export function openSettingWindow() {
|
||||||
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
|
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
|
||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
@@ -37,6 +38,8 @@ export function openSettingWindow() {
|
|||||||
// dock icon is clicked and there are no other windows open.
|
// dock icon is clicked and there are no other windows open.
|
||||||
if (BrowserWindow.getAllWindows().length === 0) createMainWindow()
|
if (BrowserWindow.getAllWindows().length === 0) createMainWindow()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
gtag('ui_ready')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { machineId } from 'node-machine-id'
|
||||||
|
|
||||||
|
const GA_ENDPOINT = 'https://www.google-analytics.com/mp/collect'
|
||||||
|
const GA_DEBUG_ENDPOINT = 'https://www.google-analytics.com/debug/mp/collect'
|
||||||
|
|
||||||
|
// Get via https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports
|
||||||
|
const MEASUREMENT_ID = '<measurement_id>'
|
||||||
|
const API_SECRET = '<api_secret>'
|
||||||
|
const DEFAULT_ENGAGEMENT_TIME_MSEC = 100
|
||||||
|
|
||||||
|
// Duration of inactivity after which a new session is created
|
||||||
|
const SESSION_EXPIRATION_IN_MIN = 30
|
||||||
|
|
||||||
|
let sessionData: null | {
|
||||||
|
session_id: string
|
||||||
|
timestamp: number
|
||||||
|
} = null
|
||||||
|
|
||||||
|
class Analytics {
|
||||||
|
private debug: boolean
|
||||||
|
constructor(debug = false) {
|
||||||
|
this.debug = debug
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the client id, or creates a new one if one doesn't exist.
|
||||||
|
// Stores client id in local storage to keep the same client id as long as
|
||||||
|
// the extension is installed.
|
||||||
|
async getOrCreateClientId() {
|
||||||
|
return machineId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current session id, or creates a new one if one doesn't exist or
|
||||||
|
// the previous one has expired.
|
||||||
|
async getOrCreateSessionId() {
|
||||||
|
// Use storage.session because it is only in memory
|
||||||
|
const currentTimeInMs = Date.now()
|
||||||
|
// Check if session exists and is still valid
|
||||||
|
if (sessionData && sessionData.timestamp) {
|
||||||
|
// Calculate how long ago the session was last updated
|
||||||
|
const durationInMin = (currentTimeInMs - sessionData.timestamp) / 60000
|
||||||
|
// Check if last update lays past the session expiration threshold
|
||||||
|
if (durationInMin > SESSION_EXPIRATION_IN_MIN) {
|
||||||
|
// Clear old session id to start a new session
|
||||||
|
sessionData = null
|
||||||
|
} else {
|
||||||
|
// Update timestamp to keep session alive
|
||||||
|
sessionData.timestamp = currentTimeInMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sessionData) {
|
||||||
|
// Create and store a new session
|
||||||
|
sessionData = {
|
||||||
|
session_id: currentTimeInMs.toString(),
|
||||||
|
timestamp: currentTimeInMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sessionData.session_id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fires an event with optional params. Event names must only include letters and underscores.
|
||||||
|
async fireEvent(
|
||||||
|
name,
|
||||||
|
params: {
|
||||||
|
session_id?: string
|
||||||
|
engagement_time_msec?: number
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
// Configure session id and engagement time if not present, for more details see:
|
||||||
|
// https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports
|
||||||
|
if (!params.session_id) params.session_id = await this.getOrCreateSessionId()
|
||||||
|
|
||||||
|
if (!params.engagement_time_msec) params.engagement_time_msec = DEFAULT_ENGAGEMENT_TIME_MSEC
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${
|
||||||
|
this.debug ? GA_DEBUG_ENDPOINT : GA_ENDPOINT
|
||||||
|
}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
client_id: await this.getOrCreateClientId(),
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
params
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!this.debug) return
|
||||||
|
const res = await response.text()
|
||||||
|
console.log('gtag res', res)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Google Analytics request failed with an exception', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Analytics(process.env.NODE_ENV === 'development')
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import buildInfo from '../../../common/build-info.json'
|
||||||
|
|
||||||
|
type LowercaseLetter =
|
||||||
|
| 'a'
|
||||||
|
| 'b'
|
||||||
|
| 'c'
|
||||||
|
| 'd'
|
||||||
|
| 'e'
|
||||||
|
| 'f'
|
||||||
|
| 'g'
|
||||||
|
| 'h'
|
||||||
|
| 'i'
|
||||||
|
| 'j'
|
||||||
|
| 'k'
|
||||||
|
| 'l'
|
||||||
|
| 'm'
|
||||||
|
| 'n'
|
||||||
|
| 'o'
|
||||||
|
| 'p'
|
||||||
|
| 'q'
|
||||||
|
| 'r'
|
||||||
|
| 's'
|
||||||
|
| 't'
|
||||||
|
| 'u'
|
||||||
|
| 'v'
|
||||||
|
| 'w'
|
||||||
|
| 'x'
|
||||||
|
| 'y'
|
||||||
|
| 'z'
|
||||||
|
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
||||||
|
type Underscore = '_'
|
||||||
|
|
||||||
|
type ValidChar = LowercaseLetter | Digit | Underscore
|
||||||
|
|
||||||
|
type StartsWithLowercase<S extends string> = S extends `${LowercaseLetter}${string}` ? S : never
|
||||||
|
type EndsWithLowercaseOrDigit<S extends string> = S extends `${string}${LowercaseLetter | Digit}`
|
||||||
|
? S
|
||||||
|
: never
|
||||||
|
type ContainsOnlyValidChars<S extends string> = S extends `${ValidChar}${infer Rest}`
|
||||||
|
? ContainsOnlyValidChars<Rest>
|
||||||
|
: S extends ''
|
||||||
|
? S
|
||||||
|
: never
|
||||||
|
|
||||||
|
type ValidString<S extends string> =
|
||||||
|
StartsWithLowercase<S> extends never
|
||||||
|
? never
|
||||||
|
: EndsWithLowercaseOrDigit<S> extends never
|
||||||
|
? never
|
||||||
|
: ContainsOnlyValidChars<S> extends never
|
||||||
|
? never
|
||||||
|
: S
|
||||||
|
|
||||||
|
function getCommonParams() {
|
||||||
|
return {
|
||||||
|
app_version: buildInfo.version,
|
||||||
|
app_build_hash: buildInfo.buildHash,
|
||||||
|
t: Number(new Date())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function gtag<T extends string>(
|
||||||
|
name: ValidString<T>,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
params: Record<string, any> = {}
|
||||||
|
) {
|
||||||
|
params = {
|
||||||
|
...getCommonParams(),
|
||||||
|
...params
|
||||||
|
}
|
||||||
|
// ServiceWorker环境下直接调用上报函数
|
||||||
|
const reporter = (await import('./Analytics')).default
|
||||||
|
return reporter.fireEvent(name, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire a page view event.
|
||||||
|
export function gtagPageView(
|
||||||
|
page_title = document?.title ?? '',
|
||||||
|
page_location = location.href,
|
||||||
|
additionalParams = {}
|
||||||
|
) {
|
||||||
|
return gtag('page_view', {
|
||||||
|
page_location,
|
||||||
|
page_title,
|
||||||
|
...getCommonParams(),
|
||||||
|
...additionalParams
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire an error event.
|
||||||
|
export function gtagApplicationError(error: string, additionalParams = {}) {
|
||||||
|
// Note: 'error' is a reserved event name and cannot be used
|
||||||
|
// see https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#reserved_names
|
||||||
|
return gtag('application_error', {
|
||||||
|
error,
|
||||||
|
...getCommonParams(),
|
||||||
|
...additionalParams
|
||||||
|
})
|
||||||
|
}
|
||||||
Generated
+7
@@ -126,6 +126,9 @@ importers:
|
|||||||
electron-updater:
|
electron-updater:
|
||||||
specifier: ^6.1.7
|
specifier: ^6.1.7
|
||||||
version: 6.1.7
|
version: 6.1.7
|
||||||
|
node-machine-id:
|
||||||
|
specifier: ^1.1.12
|
||||||
|
version: 1.1.12
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@electron-toolkit/eslint-config':
|
'@electron-toolkit/eslint-config':
|
||||||
specifier: ^1.0.2
|
specifier: ^1.0.2
|
||||||
@@ -4621,6 +4624,10 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/node-machine-id@1.1.12:
|
||||||
|
resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/node-releases@2.0.14:
|
/node-releases@2.0.14:
|
||||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|||||||
Reference in New Issue
Block a user