Search code examples
javascriptes6-proxy

create structured clone of Proxy


I have a class that returns a Proxy from the constructor. When I try to store instances of this class in IndexedDB, or send the object using window.postMessage(), I receive an error stating that the object could not be cloned. It appears that the structured clone algorithm cannot handle Proxy objects.

The following code demonstrates the error:

class MyClass {
  constructor() {
    return new Proxy(this, {
      set(target, prop, val, receiver) {
        console.log(`"${prop}" was set to "${val}"`);
        return Reflect.set(target, prop, val, receiver);
      }
    });
  }
}

const obj = new MyClass;

try {
  window.postMessage(obj,'*');
} catch(err) {
  console.error(err);

}

Can anyone suggest a workaround for this problem? I see two potential solutions, but I don't know how I might implement them:

  1. Do not return a Proxy from the constructor, but maintain the Proxy functionality within the class declaration somehow.

  2. Alter the Proxy instance so that it works with the structured clone algorithm.

EDIT: The following, simpler code also demonstrates the structured clone error:

const p = new Proxy({}, {});
window.postMessage(p, '*');


Solution

  • You can save the original, non-proxied object in a class property, and use it when you want to pass it to postMessage. You can change the constructor to have an optional parameter which will be passed to the proxy instead of this. This way you can recreate the object by passing it to the constructor.

    class MyClass {
      constructor(original = this) {
        this.original = original;
        return new Proxy(original, {
          set(target, prop, val, receiver) {
            console.log(`"${prop}" was set to "${val}"`);
            return Reflect.set(target, prop, val, receiver);
          }
        });
      }
      export() {
        return this.original;
      }
      static import(original) {
        return new MyClass(original);
      }
    }
    
    const obj = new MyClass;
    
    obj.test = 1;
    console.log(MyClass.import(obj.export()).test);
    MyClass.import(obj.export()).test = 2;
    
    try {
      window.postMessage(obj.export(), '*');
    } catch(err) {
      console.error(err);
    }