What are Proxy Objects & how I learnt about them?

What are Proxy Objects & how I learnt about them?

I have been working on a third-party script for a client where we needed to detect headless browsers to prevent automated or controlled requests from making their way to our final data which is critical for so many reasons.

Puppeteer is a Node library which makes it easy to automate chrome/chromium. This has many use cases for testing automation.

Requests coming from puppeteer can be identified in various ways. But there is an excellent tool which kind of acts as Puppeteer's drop-in replacement but they modify the Puppeteer's instance in a way that makes it hard to detect headless instances. But how are they modifying it? That was my question as well!

Here is what they are using to modify that. Doesn't have anything to do with what is the actual implementation of hiding those traces to detect headless but rather what they used to add that implementation!

Introducing Proxy

Proxy as in our common understanding is an intermediary that can be used in place of something, or represents something.

Proxy objects are no different. They can be used in place of their target objects and may redefine the fundamental operations (traps) i.e getting and setting or defining properties of that object.

Where can I use them?

Since they can intercept any calls to getters & setters of properties as well, they may be used to transform the outcomes and can be useful in redefining logging, formatting, validation, creating drop-in replacements and offering more functionality

Important terminologies

target is an object which you want to proxy

handler the object which contains traps. It may be empty

traps the actual implementation of what to do when intercepted

How to create a Proxy Object?

const proxiedObject = new Proxy(targetObj, handler);

Let's see some examples

const target = {
  a: 1,
  b: 2
}
// intercept getter for all properties
const handler0 = {
  get(target, prop, receiver) {
     return "hello";
  }
}

const iReturnHelloInAnyCase = new Proxy(target, handler0);
iReturnHelloInAnyCase.a // 'hello'
iReturnHelloInAnyCase.b // 'hello

// intercept getter, but process a specific prop
const handler1 = {
  get(target, prop, receiver) {
    if (prop === "a")
        return 'hello'
    return Reflect.get(...arguments); // forward the call to target for other properties except a;
  }
}
// usage
const iOnlyRetunHelloOnA = new Proxy(target, handler1);
iOnlyRetunHelloOnA.a // 'hello'
iOnlyRetunHelloOnA.b // 2

// intercepting setter
const handler2 = {
 set(obj, prop, value) {
    if (!Number.isInteger(value))
        throw new Error('Provide a number')
    // update the target object
    obj[prop] = value + 2;
    return true;
 }
}

const iAddTwoToWhateverYouSendMe = new Proxy(target, handler2);
iAddTwoToWhateverYouSendMe.a = 1;
iAddTwoToWhateverYouSendMe.a // 3 (coz it adds 2 to whatever you send)

// an empty handler
const emptyHandler = {};
const iReturnWhateverTheTargetHas = new Proxy(target, emptyHandler);
iReturnWhateverTheTargetHas.a // 3
iReturnWhateverTheTargetHas.b // 2

/* No Impact on target object */
target.a // 3 (this is because iAddTwoToWhateverYouSendMe.a modified it. otherwise it would simply be 1
target.b // 2

Notes

  • Proxy object can be created using new Proxy(target, handler)

  • An object can be proxied many times by defining different handlers

  • An empty handler object i.e {} simply means forward the call to target object as is (check iReturnWhateverTheTargetHas above)

  • You may only intercept get or set. Not necessary to redefine/intecept all of them. Anything missing in your handler will be processed by target object

  • The proxied object does not effect target object. Since it's a different object

  • You may choose to intercept an operation but the process only a specific property (see handler1 it only returns hello for property a. While forwards call to target object using Reflect)

Further Reading

This was just an introduction to a possibility. Read more about Proxies & their limitations at MDN