From d31a0b309aca4407d4548010b991c5cb41eb86a2 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Fri, 7 Aug 2026 09:40:58 +0200 Subject: [PATCH] fix(cloudflare): Try/catch on non-configurable prototypes --- packages/cloudflare/src/durableobject.ts | 6 ++- .../cloudflare/test/durableobject.test.ts | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/durableobject.ts b/packages/cloudflare/src/durableobject.ts index d0f740c214b2..60236033d698 100644 --- a/packages/cloudflare/src/durableobject.ts +++ b/packages/cloudflare/src/durableobject.ts @@ -327,7 +327,11 @@ function instrumentPrototypeRpcMethods(obj: object, excludedMethods?: ReadonlySe } const wrapped = createRpcPrototypeWrapper(methodName, descriptor.value as UncheckedMethod); - Object.defineProperty(prototype, methodName, { ...descriptor, value: wrapped }); + + try { + Object.defineProperty(prototype, methodName, { ...descriptor, value: wrapped }); + } catch {} + // Only the wrapper is marked, not the original method: `wrapMethodWithSentry` resolves // through the same global map and must not resolve the original to this wrapper, // which would recurse. diff --git a/packages/cloudflare/test/durableobject.test.ts b/packages/cloudflare/test/durableobject.test.ts index 3830e779d4e3..bb6b79717664 100644 --- a/packages/cloudflare/test/durableobject.test.ts +++ b/packages/cloudflare/test/durableobject.test.ts @@ -348,6 +348,43 @@ describe('instrumentDurableObjectWithSentry', () => { expect(obj.rpcMethod()).toBe('result'); }); + it('skips non-configurable prototype methods instead of failing construction', () => { + const testClass = class { + sealedMethod() { + return 'sealed-result'; + } + + rpcMethod() { + return 'rpc-result'; + } + }; + Object.defineProperty(testClass.prototype, 'sealedMethod', { + value: testClass.prototype.sealedMethod, + writable: false, + enumerable: false, + configurable: false, + }); + const originalSealedMethod = testClass.prototype.sealedMethod; + + const instrumented = instrumentDurableObjectWithSentry( + vi.fn().mockReturnValue({ enableRpcTracePropagation: true }), + testClass as any, + ); + + let obj: any; + expect(() => { + obj = Reflect.construct(instrumented, []); + }).not.toThrow(); + + // The non-configurable method keeps its original (unwrapped) implementation + expect(testClass.prototype.sealedMethod).toBe(originalSealedMethod); + expect(obj.sealedMethod()).toBe('sealed-result'); + + // Other methods on the same prototype are still wrapped + expect(getInstrumented(obj.rpcMethod)).toBeTruthy(); + expect(obj.rpcMethod()).toBe('rpc-result'); + }); + it('does not wrap Object.prototype methods as RPC methods', () => { const testClass = class { rpcMethod() {