samiskin / redux-electron-store

⎋ A redux store enhancer that allows automatic synchronization between electron processes
MIT License
375 stars 32 forks source link

Blocking non viable json objects from main process before sending it to renderer process #32

Open ernstluring opened 7 years ago

ernstluring commented 7 years ago

Hi again,

This looks a bit silly but after I thought I solved this issue it appeared again at the exact same moments. When I store a socket and a electron process in the store (these values can't and don't need to be send to the renderer process) I get the following exception:

Uncaught Exception: TypeError: Converting circular structure to JSON at store.dispatch (...\node_modules\redux-electron-store\lib\electronBr....:45)

You mentioned before that this can happen because of the let browserStore = remote.getGlobal(globalName). I would like to hear your thoughts about this and if there is a possible solution for this. At the moment I have the temporarily solution of a second store for these things that don't use the redux enhancer.

Thanks!

samiskin commented 7 years ago

Drat, could you provide me with a code snippet of setting up a reducer and filter to show off this issue?

ernstluring commented 7 years ago

This is the store and the reducer on the renderer process side: Setting up store:

export function configureStore(preloadedState?) {
    // Data this renderer process wants to be notified of
    let filter = {
        project: true,
        application: {
            loadingText:true,
            loadingBlocking:true,
            errorText:true,
            gameState:true,
            builderState:true,
            builderErrors:true
        },
        releaseCache: true
    }

    let enhancer = compose(
        applyMiddleware(thunkMiddleware),
        electronEnhancer({filter})
    ) as GenericStoreEnhancer

    const store = createStore<IStudioStore>(
        reducers,
        preloadedState,
        enhancer
    )
    return store
}
export interface IStudioApplication {
    // The currently showing loading text
    loadingText?:string;
    loadingBlocking?:boolean;

    errorText?:string;
    // The currently selected tab
    tab:ApplicationTab;

    // The current state of the game
     gameState:GameState;

    // The current state of the builder
     builderState:BuilderState;

    // Builder errors
    builderErrors:{[file: string]: string[]}

    // The current selected tool tab
    toolTab:ToolTab
}

The reducer:

export default handleActions<IStudioApplication, any>({
    [SELECT_TAB]: (state, action) => {
        return Object.assign({}, state, {tab: action.payload})
    },
    [SELECT_TOOL_TAB]: (state, action) => <IStudioApplication>Object.assign({}, state, { toolTab: action.payload }),
}, initialState)

And setting up store on main process

export interface IStore {
    /** Holds project information */
    project: IProject,

    /** Holds application information */
    application: IApplication,

    /** Holds the release cache information */
    releaseCache: IReleaseCache
}
export interface IApplication {
    // The currently showing loading text
    loadingText?:string;
    loadingBlocking?:boolean;

    errorText?:string;

    // The current state of the game
    gameState:GameState;
    gameProcess:ChildProcess;
    gameSocket:net.Socket;

    // The current state of the builder
    builderState:BuilderState;
    builderProcess:ChildProcess;
    builderSocket:net.Socket;

    // Builder errors
    builderErrors:{[file: string]: string[]}
}
export function configureStore(preloadedState?:IStore) {
    return new Promise<Redux.Store<IStore>>((resolve, reject) => {
        let enhancer = compose(
            applyMiddleware(thunkMiddleware),
            electronEnhancer()
        ) as GenericStoreEnhancer

        const store = createStore<IStore>(
            reducers,
            preloadedState,
            enhancer
        )
        resolve(store)
    })
}
samiskin commented 7 years ago

I wasn't able to precisely reproduce your error, but I did have other errors happening right at startup, as in order to do anything I run a recursive objectDifference function on the state to find out what changed, and that breaks on recursive structures. I'm not sure how performant it would be to add more logic to check if it is recursive, as this stuff happens on every dispatch.

I guess what I would have to do is merge the objectDifference and fillShape steps such that the objectDifference logic only runs on data which passes any of the filters, and then explicitly require that no recursive data structures pass through any filter (which is already an undocumented requirement anyway).

After #33, I think the global[globalName] = () => JSON.stringify(store.getState()) will also cause a problem here. A possible solution is to have the renderer get that state using a synchronous ipc call when it registers, and have main reply with a filtered version of the state for the renderers initial state. The possible tradeoff here is that anything not passing the filter will be undefined in the renderer.

I doubt I'll be able to get to this for a while due to university, sorry 😞. Feel free to fork the repo and try out solutions yourself if its a pressing matter, and hopefully what I said in the previous paragraphs helps with that.

daviestar commented 7 years ago

Hello, I have run into a similar error but in a different scenario. Error is with this stringify:

if (shouldForwardUpdate) {
  ipcRenderer.send(
    `${globalName}-renderer-dispatch`,
    JSON.stringify({ action, clientId })
  );
}

I have a redux store with keys for movies, series, seasons and episodes, each keyed by id. In mapStateToProps if I try to piece the series, seasons and episodes back together I get the error (I realise this isn't best practice, I was just starting to flesh out the functionality):

const mapStateToProps = ({incoming: {queue: {movies, shows, seasons, episodes}}}) => ({
  movies: Object.keys(movies).map(id => movies[id]),
  shows: Object.keys(shows).map(id => shows[id])
    .map(show => {
      show.seasons = show.seasonIds
        .map(id => seasons[id])
        .map(season => {
          season.episodes = season.episodeIds
            .map(id => episodes[id])
          return season
        })
      return show
    })
})
TypeError: Converting circular structure to JSON

there's no error like this:

const mapStateToProps = ({incoming: {queue: {movies, shows, seasons, episodes}}}) => ({
  movies: Object.keys(movies).map(id => movies[id]),
  shows: Object.keys(shows).map(id => shows[id])
})