Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StoreDevtools: window postMessage, object Position cannot be cloned #825

Closed
montella1507 opened this issue Feb 16, 2018 · 25 comments
Closed

Comments

@montella1507
Copy link

I'm submitting a...

[x ] Bug report

What is the current behavior?

Just migrated to the newest typescript and we got new error from StoreDevTools.
"window postMessage, object Position cannot be cloned"

@montella1507
Copy link
Author

Fixed. But i would let open this issue, because it is interesting problem. Maybe it relates to some problem..

If i create an observable from window.geolocation.getPosition() and pass Position object from the callback directly to observer. it will crash in navigation in StoreDevTools.

However. if i map that position to my own new object, it works..

`
ERROR in StoreDevTools
return Observable.create(obs => {
window...getPosition((pos)=> {
obs.next(pos);
obs.complete();
});
});

OK
return Observable.create(obs => {
window...getPosition((pos)=> {
obs.next({
coords: {
...
}
});
obs.complete();
});
});
`

it looks like StoreDevTools is trying to copy the object when it is already freed:?

Feel free to close the issue

@warapitiya
Copy link

What is the fix here? I'm getting an error like this too.

@montella1507
Copy link
Author

WELL... do not use Position object from geolocation callback directly in your code.. Just copy Longtitude, lattitude values to new object.

obs.next({ coords: { long : original.coords.long, lat : original.coords.lat, }

BTW-... this wasnt the only one problem we had.. We had to remove ALL "Position" type in our code, else we got "Position is undefined" error in runtime - lol..

I think there may be problem in Typescript lib.dom.ts where are Position 2times: as a interface and as a value.

@warapitiya
Copy link

ok. looks like this is not the same issue for me! But when i disable the Redux extension everything works normally.

Below is the error message I get:

ERROR Error: Uncaught (in promise): DataCloneError: Failed to execute 'postMessage' on 'Window': function DashboardComponent(title, activatedRoute, dashboardApiService, userService, rootStore, store, route, m...<omitted>... } could not be cloned.

@montella1507
Copy link
Author

Well.. this looks littlebit like your own mistake, are you pushiung to observer a component?

@Overdozed
Copy link

Overdozed commented Feb 16, 2018

same issue if redux extension is on after updating from
"@ngrx/effects": "^5.0.1",
"@ngrx/store": "^5.0.0",
"@ngrx/store-devtools": "^5.0.1",

to 5.1.0

works like a charm after downgrading version back

@warapitiya
Copy link

warapitiya commented Feb 17, 2018

@Overdozed same thing happened to me. but after execute

$ yarn info @ngrx/effects

The version 5.0.1 not there at all

@warapitiya
Copy link

@Overdozed any fix?

@brandonroberts
Copy link
Member

brandonroberts commented Feb 18, 2018

Can someone post a reproduction using stackblitz?

@warapitiya
Copy link

warapitiya commented Feb 19, 2018

@brandonroberts for now I will attach the proper error message here. I will get this error message only when Redux devtool extension is enabled on Chrome and only if I executing the action GO with relativeTo extra.

this.store.dispatch(new fromRoot.Go({
      path: ['11', '66'],
      extras: {
        relativeTo: this.route
      }
    }));

screen shot 2018-02-19 at 1 39 04 pm

@albohlabs
Copy link

albohlabs commented Feb 20, 2018

The structured clone algorithm [1] which is used by window.postMessage [2] is not able to clone function objects.
In my case i have a proxy object in the action payload.

The error is ease to reproduce by the following:

const a = {
  abc () {}
};
window.postMessage(a, '*')

[1] https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
[2] https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

@albohlabs
Copy link

albohlabs commented Feb 20, 2018

In my case the devtoolsExtension.send method is called with a function object like this.devtoolsExtension.send(null, {abc(){}}, this.config, this.instanceId);. But i can't reproduce it atm on stackblitz. Looks like there is a data structure which results in this issue ¯_(ツ)_/¯

@brandonroberts
Copy link
Member

There are two things contributing to this issue. Before #790 landed, the serialize option was set to false, handling circular references (such as those provided if you dispatch an action with the ActivatedRoute. The devtools performance is much better as a result, but now things like the ActivatedRoute (which is not serializable) gets mangled when trying to send it to the devtools

My recommendation is that if you're dispatching actions with the ActivatedRoute or some non-serializable entity, use the actionSanitizer and stateSanitizer options introduced in 5.x to replace the ActivatedRoute in the payload with arbitrary placeholder text.

@warapitiya
Copy link

@brandonroberts could u please share a possible usage code with ngrx. I'm bit confused.

@warapitiya
Copy link

@brandonroberts after few hours, I think I figured it out! works like charm 👍

@brandonroberts
Copy link
Member

@warapitiya cool. Will you share what you came up with? Would be good to reference in this issue and something we could look at adding to the docs.

@warapitiya
Copy link

I just add actionSanitizer to instrument

StoreDevtoolsModule.instrument({
      maxAge: 25, //  Retains last 25 states
      actionSanitizer: (action: { type: string; payload?: any }, id: number): Action => action.type === '[Router] Go' && action.payload ?
        {...action, payload: '<<LONG_BLOB>>'} : action
    }),

@wesselvdv
Copy link

wesselvdv commented Feb 23, 2018

I actually went with a similar, but smaller solution since it wasn't entirely clear for me which action was causing the issue:

StoreDevtoolsModule.instrument({
  maxAge: 25,
  actionSanitizer: action => JSON.parse(stringify(action))
})

Only issue I can think of with this approach is circular references, but I have yet to encounter that.

EDIT:

To make it sturdy enough to handle circular references I added the follow wrapper for JSON.stringify:

export function stringify(obj: any, replacer?, spaces?, cycleReplacer?): string {
  return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces);
}

function serializer(replacer, cycleReplacer) {
  const stack = [];
  const keys = [];

  if (cycleReplacer == null) {
    cycleReplacer = (key, value) => {
      if (stack[0] === value) {
        return '[Circular ~]';
      }
      return `[Circular ~.${keys.slice(0, stack.indexOf(value)).join(".")}]`
    }
  }

  return function (key, value) {
    if (stack.length > 0) {
      const thisPos = stack.indexOf(this);
      ~thisPos ? stack.splice(thisPos + 1) : stack.push(this);
      ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);
      if (~stack.indexOf(value)) {
        value = cycleReplacer.call(this, key, value);
      }
    } else {
      stack.push(value)
    }

    return replacer == null ? value : replacer.call(this, key, value)
  }
}

The above is a shameless steal of the package json-stringify-safe, but adapted to typescript.

@brandonroberts
Copy link
Member

@wesselvdv cool. I know the devtools extension uses https://github.com/kolodny/jsan underneath for serialization that supports circular references and such.

@warapitiya
Copy link

Anyway, I noticed when serializing there is a significant performance hit.

@jmannau
Copy link

jmannau commented Mar 1, 2018

I have been receiving the same error. (Error message below) and have narrowed it down to occurring when the @ngrx/store/update-reducers action is dispatched. The error occurs on downstream of

this.devtoolsExtension.send(null, state, this.config, this.instanceId);

Interestingly if I sanitise all actions to a simple object with only the type attribute, the error doesn't occur. eg:

const ActionSanitizer = (action: Action) => {
  return { type: action.type };
};

It is occurring when a lazily-loaded redux store is injected.

Error Message

ERROR Error: Uncaught (in promise): DataCloneError: Failed to execute 'postMessage' on 'Window': function () {
            if (callback) {
                callback();
            }
        } could not be cloned.
Error: Failed to execute 'postMessage' on 'Window': function () {
            if (callback) {
                callback();
            }
        } could not be cloned.
    at post (<anonymous>:1:19703)
    at toContentScript (<anonymous>:1:20486)
    at Function.sendMessage [as send] (<anonymous>:1:20741)
    at DevtoolsExtension.notify (store-devtools.es5.js:248)
    at ScanSubscriber.StoreDevtools.applyOperators.state [as accumulator] (store-devtools.es5.js:705)
    at ScanSubscriber._tryNext (scan.js:111)
    at ScanSubscriber._next (scan.js:104)
    at ScanSubscriber.Subscriber.next (Subscriber.js:89)
    at ScanSubscriber.rxjs_Subscriber.Subscriber.next (zone-patch-rxjs.js:165)
    at WithLatestFromSubscriber._next (withLatestFrom.js:115)
    at post (<anonymous>:1:19703)
    at toContentScript (<anonymous>:1:20486)
    at Function.sendMessage [as send] (<anonymous>:1:20741)
    at DevtoolsExtension.notify (store-devtools.es5.js:248)

@rhalff
Copy link

rhalff commented Mar 14, 2018

Had a similar error, but after upgrading to 5.2.0 the error is gone, I think this commit fixed it: a5dcdb1

@tmonte
Copy link

tmonte commented Mar 23, 2018

I'm still getting this error on 5.2.0, exact same problem as @jmannau. Any solutions?

@jmannau
Copy link

jmannau commented Apr 10, 2018

@tmonte apologies for the late reply.

I used an ActionSantizer as per #825 (comment) to remove the action payload that caused the error. In my case, there were 2 actions that had payloads that error on serialisation. One had a geoposition object in the payload. The second had a callback function as a property. It took some time to narrow it down to those 2 actions.

My ActionSanitizer is the following:

export function ActionSanitizer(action: Action) {
  const { type, payload } = <any>action;
  switch (action.type) {
    case SEARCH: {
      // This action ontains onComplete callback which cannot be cloned and causes storedectools to error
      return { type, payload };
    }
    case UPDATE_LOCATION: {
      // This action ontains geolocation position object callback which cannot be cloned and causes storedectools to error
      return { type, payload: JSON.stringify(payload) };
    }
  }
  return action;
}

@dotmobo
Copy link

dotmobo commented Apr 12, 2018

Hi guys !
I fixed a similar bug with the following solutions :

  • Migration from ngrx 5.1.0 to 5.2.0
  • Use the following StoreDevtoolsModule in the app.module.ts :
StoreDevtoolsModule.instrument({
      maxAge: 10,
      stateSanitizer: (oriState: any, id: number): any => {
        const { router, ...newState } = oriState;
        const { state, ...newRouter } = router || { state: null };
        const { _root, ...newRouterState } = state || { _root: null };
        return {
          ...newState,
          router: {
            ...newRouter,
            state: newRouterState
          }
        };
      }
    })

In my case, i removed _root from the router state object, because it contained a circular importation !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants