From 32ad7c54c521c7c238d9e5e2175f180c117e5e2b Mon Sep 17 00:00:00 2001 From: geekgeekrun Date: Sat, 24 Feb 2024 11:47:03 +0800 Subject: [PATCH] WIP: add logic to locate user active installed chromium browser, make it can bu used for our product rather than download new one. TODO: test --- packages/ui/package.json | 1 + ...check-and-download-puppeteer-executable.ts | 10 ++-- ...-and-locate-existed-chromium-executable.ts | 17 ++++++ .../history-utils.ts | 52 +++++++++++++++++++ .../CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts | 35 ++++++++++++- .../flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts | 4 +- packages/ui/src/main/window/mainWindow.ts | 21 ++++---- pnpm-lock.yaml | 12 +++++ 8 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-locate-existed-chromium-executable.ts create mode 100644 packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/history-utils.ts diff --git a/packages/ui/package.json b/packages/ui/package.json index aa96e34..b37c1d2 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -30,6 +30,7 @@ "JSONStream": "^1.3.5", "electron-updater": "^6.1.7", "element-plus": "^2.5.5", + "find-chrome-bin": "^2.0.1", "normalize.css": "^8.0.1", "vue-router": "^4.2.5" }, diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-download-puppeteer-executable.ts b/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-download-puppeteer-executable.ts index acf52b1..d5d5068 100644 --- a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-download-puppeteer-executable.ts +++ b/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-download-puppeteer-executable.ts @@ -4,6 +4,7 @@ import * as fs from 'node:fs' import type { InstalledBrowser } from '@puppeteer/browsers' import { is } from '@electron-toolkit/utils' import electron from 'electron' +import { saveLastUsedAndAvailableBrowserPath } from './history-utils' const expectBuildId = process.env.EXPECT_CHROME_FOR_PUPPETEER_BUILD_ID || '121.0.6167.85' const cacheDir = path.join( @@ -31,7 +32,7 @@ const getPuppeteerManagerModule = async () => { return puppeteerManager } -export const getExpectPuppeteerExecutablePath = async () => { +export const getExpectCachedPuppeteerExecutablePath = async () => { const puppeteerManager = await getPuppeteerManagerModule() return puppeteerManager.computeExecutablePath({ @@ -41,9 +42,9 @@ export const getExpectPuppeteerExecutablePath = async () => { }) } -export const checkPuppeteerExecutable = async () => { +export const checkCachedPuppeteerExecutable = async () => { try { - const executablePath = await getExpectPuppeteerExecutablePath() + const executablePath = await getExpectCachedPuppeteerExecutablePath() return fs.existsSync(executablePath) } catch { // should limit [ERR_MODULE_NOT_FOUND] @@ -59,7 +60,7 @@ const checkAndDownloadPuppeteerExecutable = async ( ) => { const puppeteerManager = await getPuppeteerManagerModule() let installedBrowser: InstalledBrowser - if (!(await checkPuppeteerExecutable())) { + if (!(await checkCachedPuppeteerExecutable())) { try { await options.confirmContinuePromise } catch { @@ -84,6 +85,7 @@ const checkAndDownloadPuppeteerExecutable = async ( }) ).find((it) => it.buildId === expectBuildId)! } + await saveLastUsedAndAvailableBrowserPath(await getExpectCachedPuppeteerExecutablePath()) return installedBrowser } diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-locate-existed-chromium-executable.ts b/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-locate-existed-chromium-executable.ts new file mode 100644 index 0000000..6dcdce7 --- /dev/null +++ b/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-locate-existed-chromium-executable.ts @@ -0,0 +1,17 @@ +import { findChrome } from "find-chrome-bin"; +import * as os from 'node:os' + +export default async function findAndLocateExistedChromiumExecutable() { + // For windows, try to find Edge(chromium) + if (os.platform() === 'win32') { + // TODO: handle windows + } + // For other, use findChrome + const targetBrowser = await findChrome({}) + if (!targetBrowser?.executablePath) { + throw new Error('NO_EXPECT_CHROMIUM_FOUND') + } + return { + path: targetBrowser.executablePath + } +} diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/history-utils.ts b/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/history-utils.ts new file mode 100644 index 0000000..efe4b6a --- /dev/null +++ b/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/history-utils.ts @@ -0,0 +1,52 @@ +import * as path from 'path' +import * as os from 'os' +import * as fs from 'fs' +import * as fsPromise from 'fs/promises' + +const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun') +export const lastUsedBrowserRecordFilePath = path.join( + runtimeFolderPath, + 'last-used-browser-record' +) +/** + * check if last used browser is still available. + * + * look if last used one is exist, maybe it's downloaded by puppeteer + * immediately return its path + * else remove its history + * @returns + */ +export const getLastUsedAndAvailableBrowserPath = async (): Promise => { + if (!fs.existsSync(lastUsedBrowserRecordFilePath)) { + return null + } + try { + const fileContent = (await fsPromise.readFile(lastUsedBrowserRecordFilePath)).toString() + if (!fileContent || !fs.existsSync(fileContent)) { + await removeLastUsedAndAvailableBrowserPath() + return null + } + return fileContent + } catch { + await removeLastUsedAndAvailableBrowserPath() + return null + } +} + +export const saveLastUsedAndAvailableBrowserPath = async (pathToBrowser: string) => { + try { + if (!fs.existsSync(runtimeFolderPath)) { + await fsPromise.mkdir(runtimeFolderPath) + } + await fsPromise.writeFile(lastUsedBrowserRecordFilePath, pathToBrowser) + } catch { + console.warn('lastUsedBrowserRecordFile write error') + } +} + +export const removeLastUsedAndAvailableBrowserPath = async () => { + if (!fs.existsSync(lastUsedBrowserRecordFilePath)) { + return + } + await fsPromise.unlink(lastUsedBrowserRecordFilePath) +} diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts b/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts index 89c5a17..adc4525 100644 --- a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts +++ b/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts @@ -1,12 +1,45 @@ import { app } from 'electron' -import checkAndDownloadPuppeteerExecutable from './check-and-download-puppeteer-executable' +import checkAndDownloadPuppeteerExecutable, { + checkCachedPuppeteerExecutable, + getExpectCachedPuppeteerExecutablePath +} from './check-and-download-puppeteer-executable' import * as net from 'net' import { pipeWriteRegardlessError } from '../utils/pipe' +import { + removeLastUsedAndAvailableBrowserPath, + getLastUsedAndAvailableBrowserPath, + saveLastUsedAndAvailableBrowserPath +} from './history-utils' +import findAndLocateExistedChromiumExecutable from './check-and-locate-existed-chromium-executable' export enum DOWNLOAD_ERROR_EXIT_CODE { NO_ERROR = 0, DOWNLOAD_ERROR = 1 } +export const getAnyAvailablePuppeteerExecutablePath = async (): Promise => { + const lastUsedOnePath = await getLastUsedAndAvailableBrowserPath() + if (lastUsedOnePath) { + return lastUsedOnePath + } + // find existed browser - the one maybe actively installed by user or ship with os like Edge on windows + try { + const existedOnePath = (await findAndLocateExistedChromiumExecutable()).path + await saveLastUsedAndAvailableBrowserPath(existedOnePath) + // save its path + return existedOnePath + } catch { + console.log('no existed browser path found') + } + // find existed browser - the fallback one + if (await checkCachedPuppeteerExecutable()) { + return await getExpectCachedPuppeteerExecutablePath() + } + + // if no one available, then return null and remove last used browser + await removeLastUsedAndAvailableBrowserPath() + return null +} + export const checkAndDownloadDependenciesForInit = async () => { process.on('disconnect', () => app.exit()) app.dock.hide() diff --git a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts index 409c93d..d883ef7 100644 --- a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts +++ b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts @@ -4,7 +4,7 @@ import { SyncHook, AsyncSeriesHook } from 'tapable' import { readConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' import * as net from 'net' import { - checkPuppeteerExecutable, + checkCachedPuppeteerExecutable, } from './CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-download-puppeteer-executable' import { pipeWriteRegardlessError } from './utils/pipe' @@ -51,7 +51,7 @@ export const runAutoChat = async () => { return } - const isPuppeteerExecutable = await checkPuppeteerExecutable() + const isPuppeteerExecutable = await checkCachedPuppeteerExecutable() if (!isPuppeteerExecutable) { app.exit(1) return diff --git a/packages/ui/src/main/window/mainWindow.ts b/packages/ui/src/main/window/mainWindow.ts index da47326..3f3e93d 100644 --- a/packages/ui/src/main/window/mainWindow.ts +++ b/packages/ui/src/main/window/mainWindow.ts @@ -9,13 +9,12 @@ import { writeConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' import { ChildProcess } from 'child_process' -import { - checkPuppeteerExecutable, - getExpectPuppeteerExecutablePath -} from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/check-and-download-puppeteer-executable' import * as JSONStream from 'JSONStream' -import { DOWNLOAD_ERROR_EXIT_CODE } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES' -let mainWindow: BrowserWindow = null +import { + DOWNLOAD_ERROR_EXIT_CODE, + getAnyAvailablePuppeteerExecutablePath +} from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES' +let mainWindow: BrowserWindow | null = null export function createMainWindow(): void { // Create the browser window. @@ -95,7 +94,7 @@ export function createMainWindow(): void { const subProcessEnv = { ...process.env, MAIN_BOSSGEEKGO_UI_RUN_MODE: 'geekAutoStartWithBoss', - PUPPETEER_EXECUTABLE_PATH: await getExpectPuppeteerExecutablePath() + PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutablePath())! } subProcessOfPuppeteer = childProcess.spawn(process.argv[0], process.argv.slice(1), { env: subProcessEnv, @@ -130,9 +129,11 @@ export function createMainWindow(): void { }) ipcMain.handle('check-dependencies', async () => { - const [puppeteerExecutableAvailable] = await Promise.all([checkPuppeteerExecutable()]) + const [anyAvailablePuppeteerExecutablePath] = await Promise.all([ + getAnyAvailablePuppeteerExecutablePath() + ]) return { - puppeteerExecutableAvailable + puppeteerExecutableAvailable: !!anyAvailablePuppeteerExecutablePath } }) @@ -144,7 +145,7 @@ export function createMainWindow(): void { const subProcessEnv = { ...process.env, MAIN_BOSSGEEKGO_UI_RUN_MODE: 'checkAndDownloadDependenciesForInit', - PUPPETEER_EXECUTABLE_PATH: await getExpectPuppeteerExecutablePath() + PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutablePath())! } subProcessOfCheckAndDownloadDependencies = childProcess.spawn( process.argv[0], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1812c7b..e372138 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,9 @@ importers: element-plus: specifier: ^2.5.5 version: 2.5.5(vue@3.4.15) + find-chrome-bin: + specifier: ^2.0.1 + version: 2.0.1 normalize.css: specifier: ^8.0.1 version: 8.0.1 @@ -3056,6 +3059,15 @@ packages: to-regex-range: 5.0.1 dev: true + /find-chrome-bin@2.0.1: + resolution: {integrity: sha512-aDwC2y0dLxt0GFmQ+q8bqBCZ10VW9zYT/lNV806tRDqDAh5XpkTWulB96RKDHDuKu36m/dEvhmhD5IU237oOTg==} + engines: {node: '>=18.0.0'} + dependencies: + '@puppeteer/browsers': 1.9.0 + transitivePeerDependencies: + - supports-color + dev: false + /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'}