Platform: StoreDevtools: window postMessage, object Position cannot be cloned

Created on 16 Feb 2018  ·  25Comments  ·  Source: ngrx/platform

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"

Needs Reproduction Store Devtools

Most helpful comment

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.

All 25 comments

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

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

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.

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.

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

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

@Overdozed same thing happened to me. but after execute

$ yarn info @ngrx/effects

The version 5.0.1 not there at all

@Overdozed any fix?

Can someone post a reproduction using stackblitz?

@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

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

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 ¯_(ツ)_/¯

There are two things contributing to this issue. Before https://github.com/ngrx/platform/pull/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.

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

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

@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.

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
    }),

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.

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

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

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 https://github.com/ngrx/platform/blob/a7de2a694f22aac44cc22861094c421dd960d027/modules/store-devtools/src/extension.ts#L94

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 (:1:19703)
at toContentScript (:1:20486)
at Function.sendMessage [as send] (: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 (:1:19703)
at toContentScript (:1:20486)
at Function.sendMessage [as send] (:1:20741)
at DevtoolsExtension.notify (store-devtools.es5.js:248)

Had a similar error, but after upgrading to 5.2.0 the error is gone, I think this commit fixed it: https://github.com/ngrx/platform/commit/a5dcdb143ca1e75d291bc04338fd3225d793ac7f

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

@tmonte apologies for the late reply.

I used an ActionSantizer as per https://github.com/ngrx/platform/issues/825#issuecomment-367576543 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;
}

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 !

Was this page helpful?
0 / 5 - 0 ratings