I have to use a certain third-party library that works in the following way:
const Thing = function () {
if (!new.target) return new Thing() // Note: This previously wasn't there
const privateData = this
privateData.somethingPrivate = 123
return {
someFunction () {
return privateData.somethingPrivate
}
}
}
module.exports = Thing
As you can see, it wants to be called with new, but it then uses the auto-created this instance object as a container for private data and instead returns a fresh object (that doesn't even have the right prototype) with public functions.
So, we can do this...
const Thing = require('thing')
const thing = new Thing()
console.log(thing.someFunction()) // 123
...but we cannot do this:
thing.somethingPrivate = 456 // Does nothing
For reasons I can't explain here right now, I have to be able to access private data like somethingPrivate from the outside. Also, forking the library is not an option at the moment. I know this sounds like an XY problem but trust me, it's not. I know I'm trying to do something that I'm not "supposed" to do (and I know I have to be careful with library updates) but I need a creative solution here. (And no, this is not for nefarious purposes.)
Now, what would be needed to access somethingPrivate is gaining access to the original this that the constructor Thing is getting called with. This object is created by JS itself through the new operator. (Normally, this object is also returned from the new operator, unless the constructor returns another object, which is what happens here.)
Originally, the line if (!new.target) return new Thing() didn't exist in the library. I had a solution that looked like this (doing something similar to what the new operator would be doing, but keeping a reference to the original this):
const Thing = require('thing')
const thingPrivateData = Object.create(Thing) // Manually create instance, keep reference
const thing = Thing.call(privateData) // Call constructor on the instance
thing._private = thingPrivateData // Assign original "private" instance to a public property
Afterwards, I was able to access somethingPrivate like this:
thing._private.privateData = 456 // Works!
However, in a recent update, the library added the line if (!new.target) return new Thing() which rendered my solution useless, because my manual invokation of Thing would not set the new.target value and the library would then call itself again with new, so the this that I passed in manually would not be used and instead a new instance object would be created.
I researched into Reflect.construct and hoped I could do something like this:
const Thing = require('thing')
const thing = Reflect.construct(function () {
const obj = Thing.call(this) // This doesn't work because yet again new.target is unset
obj._private = this
return obj
}, [], Thing)
But of course it doesn't work because even though new.target is set in my (anonymous) function, it is unset again when I call into Thing, because that call again is without new/Reflect.construct.
Am I out of luck or is there some sort of creative way to get access to that original instance passed to as this to the constructor, and/or to set new.target in an invocation while also passing a custom this (and not a prototype from which one is auto-created)?