If the target is a function, two additional operations can be intercepted:
apply: Making a function call. Triggered via:
proxy(···)
proxy.call(···)
proxy.apply(···)
construct: Making a constructor call. Triggered via:
new proxy(···)
Intercepting method calls
If we want to intercept method calls via a Proxy, we are facing a challenge: There is no trap for method calls. Instead, a method call is viewed as a sequence of two operations:
A get to retrieve a function
An apply to call that function
Therefore, if we want to intercept method calls, we need to intercept two operations:
First, we intercept the get and return a function.
Second, we intercept the invocation of that function.
function createWebService(baseUrl) { return new Proxy({}, { get(target, propKey, receiver) { // Return the method to be called return () => httpGet(baseUrl + '/' + propKey); } }); }
const service = createWebService('http://example.com/data'); // Read JSON data in http://example.com/data/employees service.employees().then((jsonStr) => { const employees = JSON.parse(jsonStr); // ··· });
Revocable references
Revocable references work as follows: A client is not allowed to access an important resource (an object) directly, only via a reference (an intermediate object, a wrapper around the resource). Normally, every operation applied to the reference is forwarded to the resource. After the client is done, the resource is protected by revoking the reference, by switching it off. Henceforth, applying operations to the reference throws exceptions and nothing is forwarded, anymore.
function createRevocableReference(target) { let enabled = true; return { reference: new Proxy(target, { get(target, propKey, receiver) { if (!enabled) { throw new TypeError( `Cannot perform 'get' on a proxy that has been revoked`); } return Reflect.get(target, propKey, receiver); }, has(target, propKey) { if (!enabled) { throw new TypeError( `Cannot perform 'has' on a proxy that has been revoked`); } return Reflect.has(target, propKey); }, // (Remaining methods omitted) }), revoke: () => { enabled = false; }, }; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
function createRevocableReference(target) { let enabled = true; const handler = new Proxy({}, { get(_handlerTarget, trapName, receiver) { if (!enabled) { throw new TypeError( `Cannot perform '${trapName}' on a proxy` + ` that has been revoked`); } return Reflect[trapName]; } }); return { reference: new Proxy(target, handler), revoke: () => { enabled = false; }, }; }
Membranes build on the idea of revocable references: Libraries for safely running untrusted code wrap a membrane around that code to isolate it and to keep the rest of the system safe. Objects pass the membrane in two directions:
The untrusted code may receive objects (“dry objects”) from the outside.
Or it may hand objects (“wet objects”) to the outside.
In both cases, revocable references are wrapped around the objects. Objects returned by wrapped functions or methods are also wrapped. Additionally, if a wrapped wet object is passed back into a membrane, it is unwrapped.
Once the untrusted code is done, all of the revocable references are revoked. As a result, none of its code on the outside can be executed anymore and outside objects that it references, cease to work as well. The Caja Compiler is “a tool for making third party HTML, CSS and JavaScript safe to embed in your website”. It uses membranes to achieve this goal.
Proxies are stratified: Base level (the Proxy object) and meta level (the handler object) are separate
Proxies are used in two roles:
As wrappers, they wrap their targets, they control access to them. Examples of wrappers are: revocable resources and tracing via Proxies.
As virtual objects, they are simply objects with special behavior and their targets don’t matter. An example is a Proxy that forwards method calls to a remote object.
Proxies are shielded in two ways:
It is impossible to determine whether an object is a Proxy or not (transparent virtualization).
We can’t access a handler via its Proxy (handler encapsulation).
tell Proxies apart from non-Proxies
1 2 3 4 5 6 7 8 9 10 11 12
const proxies = new WeakSet();
function createProxy(obj) { const handler = {}; const proxy = new Proxy(obj, handler); proxies.add(proxy); return proxy; }
function isProxy(obj) { return proxies.has(obj); }