migrate cookie assistant logic to standalone window make it promisify, and call it with ghosting remainder; disable pre-check when program start run.

This commit is contained in:
geekgeekrun
2026-01-23 14:37:52 +08:00
parent 5ab7fc2ee2
commit 60dee6f435
16 changed files with 369 additions and 166 deletions

View File

@@ -1,17 +1,18 @@
export const getAutoStartChatSteps = () => [{
id: 'worker-launch',
describe: '启动子进程',
},
// {
// id: 'basic-cookie-check',
// describe: 'Cookie 格式检查',
// },
{
id: 'puppeteer-executable-check',
describe: 'Puppeteer 可执行程序检查',
},
{
id: 'login-status-check',
describe: '登录状态检查',
}
]
export const getAutoStartChatSteps = () => [
{
id: 'worker-launch',
describe: '启动子进程'
},
{
id: 'puppeteer-executable-check',
describe: 'Puppeteer 可执行程序检查'
},
{
id: 'basic-cookie-check',
describe: 'Cookie 格式检查'
},
{
id: 'login-status-check',
describe: '登录状态检查'
}
]

View File

@@ -0,0 +1,21 @@
import { ipcMain } from 'electron'
import { createCookieAssistantWindow, cookieAssistantWindow } from '../window/cookieAssistantWindow';
export async function loginWithCookieAssistant({ windowOption } = {}) {
return new Promise((resolve, reject) => {
createCookieAssistantWindow({ ...windowOption })
let processDone = false
ipcMain.once('cookie-saved', function handler() {
processDone = true
cookieAssistantWindow.close()
})
cookieAssistantWindow.once('closed', () => {
if (processDone) {
resolve(true)
} else {
reject(new Error('User cancelled login'))
}
})
})
}

View File

@@ -3,12 +3,10 @@ import path from 'path'
import * as childProcess from 'node:child_process'
import {
ensureConfigFileExist,
ensureStorageFileExist,
configFileNameList,
readConfigFile,
writeConfigFile,
readStorageFile,
writeStorageFile,
storageFilePath
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import { ChildProcess } from 'child_process'
@@ -54,6 +52,7 @@ import { checkUpdateForUi } from '../../../features/updater'
import gtag from '../../../utils/gtag'
import { daemonEE, sendToDaemon } from '../connect-to-daemon'
import { runCommon } from '../../../features/run-common'
import { loginWithCookieAssistant } from '../../../features/login-with-cookie-assistant'
export default function initIpc() {
ipcMain.handle('fetch-config-file-content', async () => {
@@ -189,17 +188,6 @@ export default function initIpc() {
return await Promise.all(promiseArr)
})
ipcMain.handle('read-storage-file', async (ev, payload) => {
ensureStorageFileExist()
return await readStorageFile(payload.fileName)
})
ipcMain.handle('write-storage-file', async (ev, payload) => {
ensureStorageFileExist()
return await writeStorageFile(payload.fileName, JSON.parse(payload.data))
})
ipcMain.handle('run-geek-auto-start-chat-with-boss', async (ev) => {
const mode = 'geekAutoStartWithBossMain'
const { runRecordId } = await runCommon({ mode })
@@ -395,56 +383,6 @@ export default function initIpc() {
)
})
let subProcessOfBossZhipinLoginPageWithPreloadExtension: ChildProcess | null = null
ipcMain.on('launch-bosszhipin-login-page-with-preload-extension', async () => {
try {
subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
} catch {
//
}
const subProcessEnv = {
...process.env,
PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutable())!.executablePath
}
subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn(
process.argv[0],
[process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`],
{
env: subProcessEnv,
stdio: [null, null, null, 'pipe', 'ipc']
}
)
subProcessOfBossZhipinLoginPageWithPreloadExtension!.stdio[3]!.pipe(JSONStream.parse()).on(
'data',
(raw) => {
const data = raw
switch (data.type) {
case 'BOSS_ZHIPIN_COOKIE_COLLECTED': {
mainWindow?.webContents.send(data.type, data)
break
}
default: {
return
}
}
}
)
subProcessOfBossZhipinLoginPageWithPreloadExtension!.once('exit', () => {
mainWindow?.webContents.send('BOSS_ZHIPIN_LOGIN_PAGE_CLOSED')
subProcessOfBossZhipinLoginPageWithPreloadExtension = null
})
})
ipcMain.on('kill-bosszhipin-login-page-with-preload-extension', async () => {
try {
subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
} catch {
//
} finally {
subProcessOfBossZhipinLoginPageWithPreloadExtension = null
}
})
ipcMain.handle('check-boss-zhipin-cookie-file', () => {
const cookies = readStorageFile('boss-cookies.json')
return checkCookieListFormat(cookies)
@@ -666,6 +604,15 @@ export default function initIpc() {
const newRelease = await checkUpdateForUi()
return newRelease
})
ipcMain.handle('login-with-cookie-assistant', async () => {
return await loginWithCookieAssistant({
windowOption: {
parent: mainWindow!,
modal: true,
show: true
}
})
})
ipcMain.handle('exit-app-immediately', () => {
app.exit(0)

View File

@@ -7,8 +7,6 @@ import { setDomainLocalStorage } from '@geekgeekrun/utils/puppeteer/local-storag
const localStoragePageUrl = `https://www.zhipin.com/desktop/`
const bossChatUiUrl = `https://www.zhipin.com/web/geek/chat`
const bossCookies = readStorageFile('boss-cookies.json')
const bossLocalStorage = readStorageFile('boss-local-storage.json')
export async function bootstrap() {
const { puppeteer } = await initPuppeteer()
@@ -28,6 +26,8 @@ export async function bootstrap() {
export async function launchBoss(browser: Browser) {
const page = (await browser.pages())[0]
const bossCookies = readStorageFile('boss-cookies.json')
const bossLocalStorage = readStorageFile('boss-local-storage.json')
//set cookies
for (let i = 0; i < bossCookies.length; i++) {
await page.setCookie(bossCookies[i])

View File

@@ -16,19 +16,29 @@ import {
getJobHireStatusRecord,
saveJobHireStatusRecord
} from '@geekgeekrun/sqlite-plugin/dist/handlers'
import { writeStorageFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import {
writeStorageFile,
readStorageFile
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import { BossInfo } from '@geekgeekrun/sqlite-plugin/dist/entity/BossInfo'
import { messageForSaveFilter } from '../../../common/utils/chat-list'
import { AUTO_CHAT_ERROR_EXIT_CODE, RECHAT_CONTENT_SOURCE, RECHAT_LLM_FALLBACK } from '../../../common/enums/auto-start-chat'
import {
AUTO_CHAT_ERROR_EXIT_CODE,
RECHAT_CONTENT_SOURCE,
RECHAT_LLM_FALLBACK
} from '../../../common/enums/auto-start-chat'
import gtag from '../../utils/gtag'
import { JobHireStatus } from '@geekgeekrun/sqlite-plugin/dist/enums';
import dayjs from 'dayjs'
import cheerio from 'cheerio'
import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon'
import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot'
// import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot'
import { checkShouldExit } from '../../utils/worker'
import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable'
import minimist from 'minimist'
import { checkCookieListFormat } from '../../../common/utils/cookie'
import { loginWithCookieAssistant } from '../../features/login-with-cookie-assistant'
import initPublicIpc from '../../utils/initPublicIpc'
const throttleIntervalMinutes =
readConfigFile('boss.json').autoReminder?.throttleIntervalMinutes ?? 10
@@ -232,6 +242,39 @@ const mainLoop = async () => {
browser = null
}
}
let bossCookies = readStorageFile('boss-cookies.json')
let cookieCheckResult = checkCookieListFormat(bossCookies)
while (!cookieCheckResult) {
try {
await loginWithCookieAssistant()
bossCookies = readStorageFile('boss-cookies.json')
cookieCheckResult = checkCookieListFormat(bossCookies)
} catch (err) {
sendToDaemon({
type: 'worker-to-gui-message',
data: {
type: 'prerequisite-step-by-step-checkstep-by-step-check',
step: {
id: 'basic-cookie-check',
status: 'rejected'
},
runRecordId
}
})
throw new Error('LOGIN_STATUS_INVALID')
}
}
sendToDaemon({
type: 'worker-to-gui-message',
data: {
type: 'prerequisite-step-by-step-checkstep-by-step-check',
step: {
id: 'basic-cookie-check',
status: 'fulfilled'
},
runRecordId
}
})
const canNotConfirmIfHasReadMsgTemplateList = [
'Boss还没查看你的消息',
'你与该职位竞争者PK情况',
@@ -250,17 +293,6 @@ const mainLoop = async () => {
// #region
if (currentPageUrl.startsWith('https://www.zhipin.com/web/user/')) {
writeStorageFile('boss-cookies.json', [])
sendToDaemon({
type: 'worker-to-gui-message',
data: {
type: 'prerequisite-step-by-step-checkstep-by-step-check',
step: {
id: 'login-status-check',
status: 'rejected'
},
runRecordId
}
})
throw new Error('LOGIN_STATUS_INVALID')
}
if (
@@ -526,6 +558,8 @@ const rerunInterval = (() => {
const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null
export async function runEntry() {
app.dock?.hide()
await app.whenReady()
initPublicIpc()
await connectToDaemon()
await sendToDaemon({
type: 'ping'
@@ -588,13 +622,29 @@ export async function runEntry() {
// handle error
if (err instanceof Error) {
if (err.message.includes('LOGIN_STATUS_INVALID')) {
await dialog.showMessageBox({
type: `error`,
message: `登录状态无效`,
detail: `请重新登录Boss直聘`
})
process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID)
break
try {
// popup login dialog, then update login status
await loginWithCookieAssistant()
} catch (err) {
await dialog.showMessageBox({
type: `error`,
message: `登录状态无效`,
detail: `请重新登录Boss直聘`
})
sendToDaemon({
type: 'worker-to-gui-message',
data: {
type: 'prerequisite-step-by-step-checkstep-by-step-check',
step: {
id: 'login-status-check',
status: 'rejected'
},
runRecordId
}
})
process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID)
break
}
}
if (err.message.includes('ERR_INTERNET_DISCONNECTED')) {
process.exit(AUTO_CHAT_ERROR_EXIT_CODE.ERR_INTERNET_DISCONNECTED)
@@ -656,3 +706,8 @@ async function storeStorage(page) {
writeStorageFile('boss-local-storage.json', localStorage)
])
}
process.on('SIGTERM', () => {
console.log('收到SIGTERM信号正在退出')
process.exit(0)
})

View File

@@ -2,6 +2,11 @@ import { BrowserWindow, ipcMain, shell } from 'electron'
import gtag from './gtag'
import buildInfo from '../../common/build-info.json'
import os from 'node:os'
import {
ensureStorageFileExist,
readStorageFile,
writeStorageFile
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
export default function initPublicIpc() {
ipcMain.on(
@@ -56,4 +61,14 @@ export default function initPublicIpc() {
}
)
})
ipcMain.handle('read-storage-file', async (ev, payload) => {
ensureStorageFileExist()
return await readStorageFile(payload.fileName)
})
ipcMain.handle('write-storage-file', async (ev, payload) => {
ensureStorageFileExist()
return await writeStorageFile(payload.fileName, JSON.parse(payload.data))
})
}

View File

@@ -0,0 +1,106 @@
import { ChildProcess } from 'child_process'
import { BrowserWindow, ipcMain } from 'electron'
import path from 'path'
import { getAnyAvailablePuppeteerExecutable } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable'
import * as childProcess from 'node:child_process'
import * as JSONStream from 'JSONStream'
export let cookieAssistantWindow: BrowserWindow | null = null
export function createCookieAssistantWindow(
opt?: Electron.BrowserWindowConstructorOptions
): BrowserWindow {
// Create the browser window.
if (cookieAssistantWindow) {
cookieAssistantWindow!.show()
}
cookieAssistantWindow = new BrowserWindow({
width: 960,
height: 720,
resizable: true,
show: false,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
sandbox: false
},
...opt
})
cookieAssistantWindow.on('ready-to-show', () => {
cookieAssistantWindow!.show()
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (process.env.NODE_ENV === 'development' && process.env['ELECTRON_RENDERER_URL']) {
cookieAssistantWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '#/cookieAssistant')
} else {
cookieAssistantWindow.loadURL(
'file://' + path.join(__dirname, '../renderer/index.html') + '#/cookieAssistant'
)
}
cookieAssistantWindow!.once('closed', () => {
cookieAssistantWindow = null
})
let subProcessOfBossZhipinLoginPageWithPreloadExtension: ChildProcess | null = null
const launchHandler = async () => {
try {
subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
} catch {
//
}
const subProcessEnv = {
...process.env,
PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutable())!.executablePath
}
subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn(
process.argv[0],
[process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`],
{
env: subProcessEnv,
stdio: [null, null, null, 'pipe', 'ipc']
}
)
subProcessOfBossZhipinLoginPageWithPreloadExtension!.stdio[3]!.pipe(JSONStream.parse()).on(
'data',
(raw) => {
const data = raw
switch (data.type) {
case 'BOSS_ZHIPIN_COOKIE_COLLECTED': {
cookieAssistantWindow?.webContents.send(data.type, data)
break
}
default: {
return
}
}
}
)
subProcessOfBossZhipinLoginPageWithPreloadExtension!.once('exit', () => {
cookieAssistantWindow?.webContents.send('BOSS_ZHIPIN_LOGIN_PAGE_CLOSED')
subProcessOfBossZhipinLoginPageWithPreloadExtension = null
})
}
ipcMain.on('launch-bosszhipin-login-page-with-preload-extension', launchHandler)
const killHandler = async () => {
try {
subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
} catch {
//
} finally {
subProcessOfBossZhipinLoginPageWithPreloadExtension = null
}
}
ipcMain.on('kill-bosszhipin-login-page-with-preload-extension', killHandler)
cookieAssistantWindow.on('closed', () => {
subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
ipcMain.off('launch-bosszhipin-login-page-with-preload-extension', launchHandler)
ipcMain.off('kill-bosszhipin-login-page-with-preload-extension', killHandler)
})
return cookieAssistantWindow!
}

View File

@@ -4,56 +4,48 @@
<img
class="block"
:class="{
'animate__animated animate__bounce animate__repeat-3':
Object.values(checkDependenciesResult).includes(false)
'animate__animated animate__bounce animate__repeat-3': true
}"
:width="256"
src="@renderer/../../../resources/icon.png"
/>
</div>
<div mt24px>愿你薪想事成</div>
<div class="h60px mt14px">
<RouterView
class="h100%"
:dependencies-status="checkDependenciesResult"
:process-waitee="downloadProcessWaitee"
></RouterView>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { onMounted, ref } from 'vue'
import { onMounted } from 'vue'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
import { gtagRenderer } from '@renderer/utils/gtag'
const router = useRouter()
const checkDependenciesResult = ref({})
const downloadProcessWaitee = ref(null)
// const checkDependenciesResult = ref({})
// const downloadProcessWaitee = ref(null)
onMounted(async () => {
gtagRenderer('bootstrap_mounted')
checkDependenciesResult.value = await electron.ipcRenderer.invoke('check-dependencies')
downloadProcessWaitee.value = Promise.withResolvers()
// checkDependenciesResult.value = await electron.ipcRenderer.invoke('check-dependencies')
// downloadProcessWaitee.value = Promise.withResolvers()
if (Object.values(checkDependenciesResult.value).includes(false)) {
gtagRenderer('dependencies_need_download')
router.replace('/downloadingDependencies')
} else {
downloadProcessWaitee.value!.resolve()
}
// if (Object.values(checkDependenciesResult.value).includes(false)) {
// gtagRenderer('dependencies_need_download')
// router.replace('/downloadingDependencies')
// } else {
// downloadProcessWaitee.value!.resolve()
// }
downloadProcessWaitee.value!.promise.then(async () => {
const isCookieFileValid = await electron.ipcRenderer.invoke('check-boss-zhipin-cookie-file')
if (!isCookieFileValid) {
gtagRenderer('found_cookie_invalid_when_bootstrap')
router.replace('/cookieAssistant')
} else {
await sleep(1000)
router.replace('/main-layout')
}
})
// downloadProcessWaitee.value!.promise.then(async () => {
// const isCookieFileValid = await electron.ipcRenderer.invoke('check-boss-zhipin-cookie-file')
// if (!isCookieFileValid) {
// gtagRenderer('found_cookie_invalid_when_bootstrap')
// router.replace('/cookieAssistant')
// } else {
await sleep(2000)
router.replace('/main-layout')
// }
// })
})
</script>

View File

@@ -0,0 +1,58 @@
<template>
<div class="h-screen flex flex-col flex-items-center flex-justify-center">
<div>
<img
class="block"
:class="{
'animate__animated animate__bounce animate__repeat-3':
Object.values(checkDependenciesResult).includes(false)
}"
:width="256"
src="@renderer/../../../resources/icon.png"
/>
</div>
<div mt24px>愿你薪想事成</div>
<div class="h60px mt14px">
<RouterView
class="h100%"
:dependencies-status="checkDependenciesResult"
:process-waitee="downloadProcessWaitee"
></RouterView>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { onMounted, ref } from 'vue'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
import { gtagRenderer } from '@renderer/utils/gtag'
const router = useRouter()
const checkDependenciesResult = ref({})
const downloadProcessWaitee = ref(null)
onMounted(async () => {
checkDependenciesResult.value = await electron.ipcRenderer.invoke('check-dependencies')
downloadProcessWaitee.value = Promise.withResolvers()
if (Object.values(checkDependenciesResult.value).includes(false)) {
gtagRenderer('dependencies_need_download')
router.replace('/downloadingDependencies')
} else {
downloadProcessWaitee.value!.resolve()
}
downloadProcessWaitee.value!.promise.then(async () => {
const isCookieFileValid = await electron.ipcRenderer.invoke('check-boss-zhipin-cookie-file')
if (!isCookieFileValid) {
gtagRenderer('found_cookie_invalid_when_bootstrap')
router.replace('/cookieAssistant')
} else {
await sleep(1000)
router.replace('/main-layout')
}
})
})
</script>

View File

@@ -201,7 +201,7 @@ const handleEditThisCookieExtensionStoreLinkClick = () => {
const handleCancel = () => {
gtagRenderer('cancel_clicked')
router.replace('/main-layout')
window.close()
}
const handleSubmit = async () => {
gtagRenderer('save_clicked')
@@ -212,7 +212,8 @@ const handleSubmit = async () => {
})
ElMessage.success('Boss直聘 Cookie 保存成功')
gtagRenderer('save_cookie_done')
router.replace('/main-layout')
window.electron.ipcRenderer.send('cookie-saved')
}
const handleBossZhipinLoginPageClosed = () => {

View File

@@ -1001,7 +1001,7 @@
pr50px
pb30px
class="no-active-definition-text-slider"
:format-tooltip="
(v) =>
typeof noActiveDefinitionMarks[v] === 'string'
@@ -1535,9 +1535,17 @@ const normalizeExpectCompanies = () => {
.join(',')
}
const handleClickLaunchLogin = () => {
const handleClickLaunchLogin = async () => {
gtagRenderer('launch_login_clicked')
router.replace('/cookieAssistant')
try {
await electron.ipcRenderer.invoke('login-with-cookie-assistant')
ElMessage({
type: 'success',
message: 'Cookie 保存成功'
})
} catch {
//
}
}
const expectCompanyTemplateList = [
{

View File

@@ -526,9 +526,17 @@ const restoreDefaultTemplate = async () => {
})
}
const handleClickLaunchLogin = () => {
const handleClickLaunchLogin = async () => {
gtagRenderer('launch_login_clicked')
router.replace('/cookieAssistant')
try {
await electron.ipcRenderer.invoke('login-with-cookie-assistant')
ElMessage({
type: 'success',
message: 'Cookie 保存成功'
})
} catch {
//
}
}
const currentStamp = ref(new Date())

View File

@@ -164,28 +164,12 @@ onUnmounted(() => {
} catch {}
}
})
const goToCheckBossZhipinCookieFile = () => router.replace('/cookieAssistant')
onMounted(() => {
electron.ipcRenderer.on('check-boss-zhipin-cookie-file', goToCheckBossZhipinCookieFile)
})
onUnmounted(() => {
electron.ipcRenderer.removeListener(
'check-boss-zhipin-cookie-file',
goToCheckBossZhipinCookieFile
)
})
;(async () => {
const checkDependenciesResult = await electron.ipcRenderer.invoke('check-dependencies')
if (Object.values(checkDependenciesResult).includes(false)) {
router.replace('/')
return
}
const isCookieFileValid = await electron.ipcRenderer.invoke('check-boss-zhipin-cookie-file')
if (!isCookieFileValid) {
router.replace('/cookieAssistant')
return
}
})()
const { buildInfo } = useBuildInfo()

View File

@@ -17,6 +17,22 @@ const routes: Array<RouteRecordRaw> = [
title: 'Boss 登录助手'
}
},
{
path: '/browserAssistant',
component: () => import('@renderer/page/BrowserAssistant/index.vue'),
meta: {
title: '浏览器助手'
},
children: [
{
path: '/downloadingDependencies',
component: () => import('@renderer/page/BrowserAssistant/page/DownloadingDependencies.vue'),
meta: {
title: '正在下载核心组件'
}
}
]
},
{
path: '/llmConfig',
component: () => import('@renderer/page/LlmConfig/index.vue'),
@@ -136,16 +152,7 @@ const routes: Array<RouteRecordRaw> = [
component: BootstrapSplash,
meta: {
title: '薪想事成'
},
children: [
{
path: '/downloadingDependencies',
component: () => import('@renderer/page/BootstrapSplash/page/DownloadingDependencies.vue'),
meta: {
title: '正在下载核心组件'
}
}
]
}
}
]