refactor(redux-service): stub main-process bridge, keep interface

ReduxService.ts becomes a stub: the class, singleton export and four
method signatures are preserved, but every method now logs an error and
returns an empty value (undefined / void) of the declared return type.
Consumers' existing null-safe branches absorb the empty return, so no
call site needs to change, and the per-call error log keeps remaining
call sites visible in production logs for staged migration to the v2
data layer (Preference / DataApi / direct IPC).

The ReduxStoreReady IPC handshake is removed on both sides: the stub no
longer reads from the renderer, so the channel has no producer or
consumer. Dropped from IpcChannel enum; renderer-side invoke is
commented out with the existing // [v2] Removed: convention.
This commit is contained in:
fullex
2026-04-21 19:22:25 -07:00
parent fc66f91139
commit e7f5dfa469
3 changed files with 30 additions and 110 deletions

View File

@@ -357,8 +357,6 @@ export enum IpcChannel {
FullscreenStatusChanged = 'fullscreen-status-changed',
ReduxStoreReady = 'redux-store-ready',
// Search Window
SearchWindow_Open = 'search-window:open',
SearchWindow_Close = 'search-window:close',

View File

@@ -1,133 +1,54 @@
/**
* @deprecated Scheduled for removal in v2.0.0
* @deprecated Stubbed in v2 — all methods log an error and return empty values.
* --------------------------------------------------------------------------
* ⚠️ NOTICE: V2 DATA&UI REFACTORING (by 0xfullex)
* --------------------------------------------------------------------------
* STOP: Feature PRs affecting this file are currently BLOCKED.
* Only critical bug fixes are accepted during this migration phase.
* The Redux store bridge has been neutralized in v2. The class and its public
* interface are preserved so existing call sites still compile and run, but
* every method now:
* - logs `logger.error(...)` with the selector / action name so remaining
* call sites are visible in production logs
* - returns an empty value (`undefined` / `void`) matching the declared
* return type; consumers' existing null-safe branches (`x?.y`, `x || []`,
* `x || null`, etc.) will naturally degrade to a "no data" path.
*
* This file is being refactored to v2 standards.
* Any non-critical changes will conflict with the ongoing work.
* The former `ipcMain.handle(IpcChannel.ReduxStoreReady, ...)` handshake has
* been removed on both sides: the stub does not read from the renderer, so
* there is no readiness state to track, and the renderer's invoke of the
* channel (renderer/src/store/index.ts) has been commented out with the
* existing `// [v2] Removed:` convention.
*
* 🔗 Context & Status:
* - Contribution Hold: https://github.com/CherryHQ/cherry-studio/issues/10954
* - v2 Refactor PR : https://github.com/CherryHQ/cherry-studio/pull/10162
* Migrate each caller to the v2 data layer (Preference / DataApi / direct
* IPC) at its own pace. Once no callers remain, delete this file.
* --------------------------------------------------------------------------
*/
import { application } from '@application'
import { loggerService } from '@logger'
import { IpcChannel } from '@shared/IpcChannel'
import { ipcMain } from 'electron'
type StoreValue = any
const logger = loggerService.withContext('ReduxService')
const STORE_READY_TIMEOUT = 10000
const STUB_ERROR_MESSAGE =
'ReduxService is stubbed in v2 — the Redux store bridge no longer works. This call site must be migrated to the new data layer.'
export class ReduxService {
private isReady = false
private resolveReady!: () => void
private readyPromise = new Promise<void>((r) => (this.resolveReady = r))
constructor() {
ipcMain.handle(IpcChannel.ReduxStoreReady, () => {
this.isReady = true
this.resolveReady()
})
}
private async waitForStoreReady(): Promise<void> {
if (this.isReady) return
let timer: ReturnType<typeof setTimeout>
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error('Timeout waiting for Redux store to be ready')), STORE_READY_TIMEOUT)
})
await Promise.race([this.readyPromise, timeout]).finally(() => clearTimeout(timer))
}
private async getWebContents(): Promise<Electron.WebContents> {
await this.waitForStoreReady()
const mainWindow = application.get('MainWindowService').getMainWindow()
if (!mainWindow) {
throw new Error('Main window is not available')
}
return mainWindow.webContents
}
// Select state from renderer process
async select<T = StoreValue>(selector: string): Promise<T> {
try {
const webContents = await this.getWebContents()
return await webContents.executeJavaScript(`
(() => {
const state = window.store.getState();
return ${selector};
})()
`)
} catch (error) {
logger.error('Failed to select store value:', error as Error)
throw error
}
logger.error(`${STUB_ERROR_MESSAGE} select('${selector}')`)
return undefined as unknown as T
}
// Dispatch action
async dispatch(action: any): Promise<void> {
try {
const webContents = await this.getWebContents()
await webContents.executeJavaScript(`window.store.dispatch(${JSON.stringify(action)})`)
} catch (error) {
logger.error('Failed to dispatch action:', error as Error)
throw error
}
logger.error(`${STUB_ERROR_MESSAGE} dispatch(type=${action?.type ?? 'unknown'})`)
}
// Get entire state tree
async getState(): Promise<any> {
try {
const webContents = await this.getWebContents()
return await webContents.executeJavaScript(`window.store.getState()`)
} catch (error) {
logger.error('Failed to get state:', error as Error)
throw error
}
logger.error(`${STUB_ERROR_MESSAGE} getState()`)
return undefined
}
// Batch dispatch actions
async batch(actions: any[]): Promise<void> {
for (const action of actions) {
await this.dispatch(action)
}
logger.error(`${STUB_ERROR_MESSAGE} batch(${actions?.length ?? 0} actions)`)
}
}
export const reduxService = new ReduxService()
/**
* @example
* async function example() {
* try {
* // Select state
* const settings = await reduxService.select('state.settings')
* logger.log('settings', settings)
*
* // Dispatch action
* await reduxService.dispatch({
* type: 'settings/updateApiKey',
* payload: 'new-api-key'
* })
*
* // Batch dispatch actions
* await reduxService.batch([
* { type: 'action1', payload: 'data1' },
* { type: 'action2', payload: 'data2' }
* ])
* } catch (error) {
* logger.error('Error:', error)
* }
* }
*/

View File

@@ -16,7 +16,8 @@
*/
import { loggerService } from '@logger'
import { combineReducers, configureStore } from '@reduxjs/toolkit'
import { IpcChannel } from '@shared/IpcChannel'
// [v2] Removed: IpcChannel only referenced by the ReduxStoreReady signal below, which is now commented out.
// import { IpcChannel } from '@shared/IpcChannel'
import { useDispatch, useSelector, useStore } from 'react-redux'
import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
@@ -124,9 +125,9 @@ export const persistor = persistStore(store, undefined, () => {
}, 0)
}
// Notify main process that Redux store is ready
void window.electron?.ipcRenderer?.invoke(IpcChannel.ReduxStoreReady)
logger.info('Redux store ready, notified main process')
// [v2] Removed: ReduxService is stubbed in v2 and no longer registers a handler for this channel.
// void window.electron?.ipcRenderer?.invoke(IpcChannel.ReduxStoreReady)
// logger.info('Redux store ready, notified main process')
})
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()