cherry-studio/src/main/services/StoreSyncService.ts
fullex 22315b7201
fix(StoreSyncService): set flag bug when register IPC handler (#5715)
fix: register IPC handler in StoreSyncService
2025-05-07 10:52:51 +08:00

117 lines
3.5 KiB
TypeScript

import { IpcChannel } from '@shared/IpcChannel'
import type { StoreSyncAction } from '@types'
import { BrowserWindow, ipcMain } from 'electron'
/**
* StoreSyncService class manages Redux store synchronization between multiple windows in the main process
* It uses singleton pattern to ensure only one sync service instance exists in the application
*
* Main features:
* 1. Manages window subscriptions for store sync
* 2. Handles IPC communication for store sync between windows
* 3. Broadcasts Redux actions from one window to all other windows
* 4. Adds metadata to synced actions to prevent infinite sync loops
*/
export class StoreSyncService {
private static instance: StoreSyncService
private windowIds: number[] = []
private isIpcHandlerRegistered = false
private constructor() {
return
}
/**
* Get the singleton instance of StoreSyncService
*/
public static getInstance(): StoreSyncService {
if (!StoreSyncService.instance) {
StoreSyncService.instance = new StoreSyncService()
}
return StoreSyncService.instance
}
/**
* Subscribe a window to store sync
* @param windowId ID of the window to subscribe
*/
public subscribe(windowId: number): void {
if (!this.windowIds.includes(windowId)) {
this.windowIds.push(windowId)
}
}
/**
* Unsubscribe a window from store sync
* @param windowId ID of the window to unsubscribe
*/
public unsubscribe(windowId: number): void {
this.windowIds = this.windowIds.filter((id) => id !== windowId)
}
/**
* Register IPC handlers for store sync communication
* Handles window subscription, unsubscription and action broadcasting
*/
public registerIpcHandler(): void {
if (this.isIpcHandlerRegistered) return
ipcMain.handle(IpcChannel.StoreSync_Subscribe, (event) => {
const windowId = BrowserWindow.fromWebContents(event.sender)?.id
if (windowId) {
this.subscribe(windowId)
}
})
ipcMain.handle(IpcChannel.StoreSync_Unsubscribe, (event) => {
const windowId = BrowserWindow.fromWebContents(event.sender)?.id
if (windowId) {
this.unsubscribe(windowId)
}
})
ipcMain.handle(IpcChannel.StoreSync_OnUpdate, (event, action: StoreSyncAction) => {
const sourceWindowId = BrowserWindow.fromWebContents(event.sender)?.id
if (!sourceWindowId) return
// Broadcast the action to all other windows
this.broadcastToOtherWindows(sourceWindowId, action)
})
this.isIpcHandlerRegistered = true
}
/**
* Broadcast a Redux action to all other windows except the source
* @param sourceWindowId ID of the window that originated the action
* @param action Redux action to broadcast
*/
private broadcastToOtherWindows(sourceWindowId: number, action: StoreSyncAction): void {
// Add metadata to indicate this action came from sync
const syncAction = {
...action,
meta: {
...action.meta,
fromSync: true,
source: `windowId:${sourceWindowId}`
}
}
// Send to all windows except the source
this.windowIds.forEach((windowId) => {
if (windowId !== sourceWindowId) {
const targetWindow = BrowserWindow.fromId(windowId)
if (targetWindow && !targetWindow.isDestroyed()) {
targetWindow.webContents.send(IpcChannel.StoreSync_BroadcastSync, syncAction)
} else {
this.unsubscribe(windowId)
}
}
})
}
}
// Export singleton instance
export default StoreSyncService.getInstance()