228 lines
6.1 KiB
TypeScript
228 lines
6.1 KiB
TypeScript
import { IpcChannel } from '@shared/IpcChannel'
|
|
import { ipcMain } from 'electron'
|
|
import { EventEmitter } from 'events'
|
|
|
|
import { windowService } from './WindowService'
|
|
|
|
type StoreValue = any
|
|
type Unsubscribe = () => void
|
|
|
|
export class ReduxService extends EventEmitter {
|
|
private stateCache: any = {}
|
|
private isReady = false
|
|
|
|
private readonly STATUS_CHANGE_EVENT = 'statusChange'
|
|
|
|
constructor() {
|
|
super()
|
|
this.setupIpcHandlers()
|
|
}
|
|
|
|
private setupIpcHandlers() {
|
|
// 监听 store 就绪事件
|
|
ipcMain.handle(IpcChannel.ReduxStoreReady, () => {
|
|
this.isReady = true
|
|
this.emit('ready')
|
|
})
|
|
|
|
// 监听 store 状态变化
|
|
ipcMain.on(IpcChannel.ReduxStateChange, (_, newState) => {
|
|
this.stateCache = newState
|
|
this.emit(this.STATUS_CHANGE_EVENT, newState)
|
|
})
|
|
}
|
|
|
|
private async waitForStoreReady(webContents: Electron.WebContents, timeout = 10000): Promise<void> {
|
|
if (this.isReady) return
|
|
|
|
const startTime = Date.now()
|
|
while (Date.now() - startTime < timeout) {
|
|
try {
|
|
const isReady = await webContents.executeJavaScript(`
|
|
!!window.store && typeof window.store.getState === 'function'
|
|
`)
|
|
if (isReady) {
|
|
this.isReady = true
|
|
return
|
|
}
|
|
} catch (error) {
|
|
// 忽略错误,继续等待
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
}
|
|
throw new Error('Timeout waiting for Redux store to be ready')
|
|
}
|
|
|
|
// 添加同步获取状态的方法
|
|
getStateSync() {
|
|
return this.stateCache
|
|
}
|
|
|
|
// 添加同步选择器方法
|
|
selectSync<T = StoreValue>(selector: string): T | undefined {
|
|
try {
|
|
// 使用 Function 构造器来安全地执行选择器
|
|
const selectorFn = new Function('state', `return ${selector}`)
|
|
return selectorFn(this.stateCache)
|
|
} catch (error) {
|
|
console.error('Failed to select from cache:', error)
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
// 修改 select 方法,优先使用缓存
|
|
async select<T = StoreValue>(selector: string): Promise<T> {
|
|
try {
|
|
// 如果已经准备就绪,先尝试从缓存中获取
|
|
if (this.isReady) {
|
|
const cachedValue = this.selectSync<T>(selector)
|
|
if (cachedValue !== undefined) {
|
|
return cachedValue
|
|
}
|
|
}
|
|
|
|
// 如果缓存中没有,再从渲染进程获取
|
|
const mainWindow = windowService.getMainWindow()
|
|
if (!mainWindow) {
|
|
throw new Error('Main window is not available')
|
|
}
|
|
await this.waitForStoreReady(mainWindow.webContents)
|
|
return await mainWindow.webContents.executeJavaScript(`
|
|
(() => {
|
|
const state = window.store.getState();
|
|
return ${selector};
|
|
})()
|
|
`)
|
|
} catch (error) {
|
|
console.error('Failed to select store value:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// 派发 action
|
|
async dispatch(action: any): Promise<void> {
|
|
const mainWindow = windowService.getMainWindow()
|
|
if (!mainWindow) {
|
|
throw new Error('Main window is not available')
|
|
}
|
|
await this.waitForStoreReady(mainWindow.webContents)
|
|
try {
|
|
await mainWindow.webContents.executeJavaScript(`
|
|
window.store.dispatch(${JSON.stringify(action)})
|
|
`)
|
|
} catch (error) {
|
|
console.error('Failed to dispatch action:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// 订阅状态变化
|
|
async subscribe(selector: string, callback: (newValue: any) => void): Promise<Unsubscribe> {
|
|
const mainWindow = windowService.getMainWindow()
|
|
if (!mainWindow) {
|
|
throw new Error('Main window is not available')
|
|
}
|
|
await this.waitForStoreReady(mainWindow.webContents)
|
|
|
|
// 在渲染进程中设置监听
|
|
await mainWindow.webContents.executeJavaScript(
|
|
`
|
|
if (!window._storeSubscriptions) {
|
|
window._storeSubscriptions = new Set();
|
|
|
|
// 设置全局状态变化监听
|
|
const unsubscribe = window.store.subscribe(() => {
|
|
const state = window.store.getState();
|
|
window.electron.ipcRenderer.send('` +
|
|
IpcChannel.ReduxStateChange +
|
|
`', state);
|
|
});
|
|
|
|
window._storeSubscriptions.add(unsubscribe);
|
|
}
|
|
`
|
|
)
|
|
|
|
// 在主进程中处理回调
|
|
const handler = async () => {
|
|
try {
|
|
const newValue = await this.select(selector)
|
|
callback(newValue)
|
|
} catch (error) {
|
|
console.error('Error in subscription handler:', error)
|
|
}
|
|
}
|
|
|
|
this.on(this.STATUS_CHANGE_EVENT, handler)
|
|
return () => {
|
|
this.off(this.STATUS_CHANGE_EVENT, handler)
|
|
}
|
|
}
|
|
|
|
// 获取整个状态树
|
|
async getState(): Promise<any> {
|
|
const mainWindow = windowService.getMainWindow()
|
|
if (!mainWindow) {
|
|
throw new Error('Main window is not available')
|
|
}
|
|
await this.waitForStoreReady(mainWindow.webContents)
|
|
try {
|
|
return await mainWindow.webContents.executeJavaScript(`
|
|
window.store.getState()
|
|
`)
|
|
} catch (error) {
|
|
console.error('Failed to get state:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// 批量执行 actions
|
|
async batch(actions: any[]): Promise<void> {
|
|
for (const action of actions) {
|
|
await this.dispatch(action)
|
|
}
|
|
}
|
|
}
|
|
|
|
export const reduxService = new ReduxService()
|
|
|
|
/** example
|
|
async function example() {
|
|
try {
|
|
// 读取状态
|
|
const settings = await reduxService.select('state.settings')
|
|
console.log('settings', settings)
|
|
|
|
// 派发 action
|
|
await reduxService.dispatch({
|
|
type: 'settings/updateApiKey',
|
|
payload: 'new-api-key'
|
|
})
|
|
|
|
// 订阅状态变化
|
|
const unsubscribe = await reduxService.subscribe('state.settings.apiKey', (newValue) => {
|
|
console.log('API key changed:', newValue)
|
|
})
|
|
|
|
// 批量执行 actions
|
|
await reduxService.batch([
|
|
{ type: 'action1', payload: 'data1' },
|
|
{ type: 'action2', payload: 'data2' }
|
|
])
|
|
|
|
// 同步方法虽然可能不是最新的数据,但响应更快
|
|
const apiKey = reduxService.selectSync('state.settings.apiKey')
|
|
console.log('apiKey', apiKey)
|
|
|
|
// 处理保证是最新的数据
|
|
const apiKey1 = await reduxService.select('state.settings.apiKey')
|
|
console.log('apiKey1', apiKey1)
|
|
|
|
// 取消订阅
|
|
unsubscribe()
|
|
} catch (error) {
|
|
console.error('Error:', error)
|
|
}
|
|
}
|
|
*/
|