Skip to content

Commit cef17b3

Browse files
author
cod1k
committed
Enhance copyExecutionContext with binding prevention
Improved the `copyExecutionContext` utility to prevent re-binding of already bound methods using a proxy. Updated test cases to validate this behavior and added support for `DurableObjectState` as a compatible context type.
1 parent 2249f68 commit cef17b3

File tree

2 files changed

+31
-10
lines changed

2 files changed

+31
-10
lines changed
Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,42 @@
1-
import { type ExecutionContext } from '@cloudflare/workers-types';
1+
import { type DurableObjectState, type ExecutionContext } from '@cloudflare/workers-types';
22

33
const kBound = Symbol.for('kBound');
44

5+
const defaultPropertyOptions: PropertyDescriptor = {
6+
enumerable: true,
7+
configurable: true,
8+
writable: true,
9+
};
10+
511
/**
612
* Clones the given execution context by creating a shallow copy while ensuring the binding of specific methods.
713
*
8-
* @param {ExecutionContext|void} ctx - The execution context to clone. Can be void.
9-
* @return {ExecutionContext|void} A cloned execution context with bound methods, or the original void value if no context was provided.
14+
* @param {ExecutionContext|DurableObjectState|void} ctx - The execution context to clone. Can be void.
15+
* @return {ExecutionContext|DurableObjectState|void} A cloned execution context with bound methods, or the original void value if no context was provided.
1016
*/
11-
export function copyExecutionContext<T extends ExecutionContext | void>(ctx: T): T {
17+
export function copyExecutionContext<T extends ExecutionContext | DurableObjectState>(ctx: T): T {
1218
if (!ctx) return ctx;
13-
return Object.assign({}, ctx, {
14-
...('waitUntil' in ctx && { waitUntil: copyBound(ctx, 'waitUntil') }),
15-
...('passThroughOnException' in ctx && { passThroughOnException: copyBound(ctx, 'passThroughOnException') }),
19+
return Object.create(ctx, {
20+
waitUntil: { ...defaultPropertyOptions, value: copyBound(ctx, 'waitUntil') },
21+
...('passThroughOnException' in ctx && {
22+
passThroughOnException: { ...defaultPropertyOptions, value: copyBound(ctx, 'passThroughOnException') },
23+
}),
1624
});
1725
}
1826

19-
function copyBound<T extends object, K extends keyof T>(obj: T, method: K): T[K] {
27+
function copyBound<T, K extends keyof T>(obj: T, method: K): T[K] {
2028
const method_impl = obj[method];
2129
if (typeof method_impl !== 'function') return method_impl;
2230
if ((method_impl as T[K] & { [kBound]?: boolean })[kBound]) return method_impl;
2331

24-
const bound = method_impl.bind(obj);
25-
return Object.defineProperty(bound, kBound, { value: true, enumerable: false });
32+
return new Proxy(method_impl.bind(obj), {
33+
get: (target, key, receiver) => {
34+
if ('bind' === key) {
35+
return () => receiver;
36+
} else if (kBound === key) {
37+
return true;
38+
}
39+
return Reflect.get(target, key, receiver);
40+
},
41+
});
2642
}

packages/cloudflare/test/copy-execution-context.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ describe('Copy of the execution context', () => {
1717

1818
expect(copy[method]()).toBe(context);
1919
});
20+
it('Copied method "rebind" prevention', async () => {
21+
const context = makeExecutionContextMock();
22+
const copy = copyExecutionContext(context);
23+
expect(copy[method].bind('test')).toBe(copy[method]);
24+
});
2025
});
2126

2227
it('No side effects', async () => {

0 commit comments

Comments
 (0)