I'm trying to trap calls to Storage
. As far as I can tell there are two ways to call either setItem
or getItem
:
sessionStorage.setItem("foo", "bar");
let item = sessionStorage.getItem("foo");
Storage.prototype.setItem.call(sessionStorage, "foo", "bar");
let item2 = Storage.prototype.getItem.call(sessionStorage, "foo");
Using sessionStorage
in Code Snippet throws a security error, so here's the code in JS Fiddle
TLDR: I can handle all cases if I do this, but it seems hacky. Is there a better/cleaner way to accomplish my goal? (NOTE: I don't have control over caller, this is why I'm covering both cases):
try {
console.log("\n\n");
let ss = new Proxy(sessionStorage, {
get: function (getTarget, p) {
if (p === "__this") {
// kinda hacky, but allows us to unwrap Proxy for binding
return getTarget;
}
console.log("sessionStorage.get proxy called")
return new Proxy(Reflect.get(getTarget, p), {
apply(applyTarget, thisArg, argArray) {
console.log("sessionStorage.get.apply called");
Reflect.apply(applyTarget, getTarget, argArray);
}
})
},
});
SP = new Proxy(Object.create(Storage.prototype), {
get: function (getTarget, p) {
console.log("Storage.get proxy called")
return new Proxy(Reflect.get(getTarget, p), {
apply(applyTarget, thisArg, argArray) {
console.log("Storage.get.apply called");
try {
return Reflect.apply(applyTarget, thisArg, argArray);
} catch (e) {
// unpack proxy if we're double-wrapped (both target and thisArg are Proxy)
return Reflect.apply(applyTarget, thisArg.__this, argArray);
}
}
})
},
});
SPW = {};
Object.defineProperty(SPW, 'prototype', {
value: SP,
configurable: false,
});
si = SPW.prototype.setItem;
gi = SPW.prototype.getItem;
si.call(ss, "foo", "3");
console.log(`Storage.prototype.getItem: ${gi.call(ss, "foo")}`);
console.log(`Storage.prototype Worked`)
} catch (e) {
console.log(`Storage.prototype Caught ${e.stack}`);
}
Result:
Storage.get proxy called
Storage.get proxy called
Storage.get.apply called
Storage.get.apply called
Storage.prototype.getItem: 3
Storage.prototype Worked
I don't see any other way than including a "hidden" __this
property so that the caller can "unwrap" the Proxy
object and get the reference to the original sessionStorage
. Is there a better way to do this?
Background
If it helps here are examples of only wrapping either sessionStorage
or Storage
, but not both:
Wrap sessionStorage
For the apply
trap, I have to pass getTarget
instead of thisArg
because the latter is Proxy
object and if I pass it an Illegal Invocation
error is thrown.
try {
let ss = new Proxy(sessionStorage, {
get: function (getTarget, p) {
console.log("sessionStorage.get called")
return new Proxy(Reflect.get(getTarget, p), {
apply(applyTarget, thisArg, argArray) {
console.log("sessionStorage.get.apply called");
Reflect.apply(applyTarget, getTarget, argArray);
}
})
},
});
ss.setItem("foo", "1");
console.log(`Proxy.sessionStorage.getItem: ${ss.getItem("foo")}`);
console.log(`Proxy.sessionStorage Worked`)
} catch (e) {
console.log(`Proxy.sessionStorage Caught ${e.stack}`);
}
Result:
sessionStorage.get called
sessionStorage.get.apply called
sessionStorage.get called
sessionStorage.get.apply called
sessionStorage.get called
sessionStorage.get.apply called
Proxy.sessionStorage.getItem: undefined
Proxy.sessionStorage Worked
Wrap Storage.prototype
Here I have to first create a new object with a separate prototype
because the prototype
property descriptor for Storage
has configurable
set to false
. Once I've done that I essentially have to do the same thing as the previous case by passing the "unwrapped" getTarget
instead of the Proxy
instance pointed to by thisArg
.
try {
console.log("\n\n");
SP = new Proxy(Object.create(Storage.prototype), {
get: function (getTarget, p) {
console.log("Storage.get proxy called")
return new Proxy(Reflect.get(getTarget, p), {
apply(applyTarget, thisArg, argArray) {
console.log("Storage.get.apply called");
try {
return Reflect.apply(applyTarget, thisArg, argArray);
} catch (e) {
console.log("apply failed when passing thisArg");
return Reflect.apply(applyTarget, getTarget, argArray);
}
}
})
},
});
SPW = {};
Object.defineProperty(SPW, 'prototype', {
value: SP,
configurable: false,
});
si = SPW.prototype.setItem;
gi = SPW.prototype.getItem;
si.call(sessionStorage, "foo", "2");
console.log(`Storage.prototype.getItem: ${gi.call(sessionStorage, "foo")}`);
console.log(`Storage.prototype Worked`)
} catch (e) {
console.log(`Storage.prototype Caught ${e.stack}`);
}
Result:
Storage.get proxy called
Storage.get proxy called
Storage.get.apply called
Storage.get.apply called
Storage.prototype.getItem: 2
Storage.prototype Worked
I think you're making this far too complicated. There's no reason to involve a proxy here, just monkey-patch the two methods: