diff --git a/.changeset/plenty-chicken-study.md b/.changeset/plenty-chicken-study.md new file mode 100644 index 000000000000..b8b06f4ac474 --- /dev/null +++ b/.changeset/plenty-chicken-study.md @@ -0,0 +1,19 @@ +--- +"wrangler": patch +--- + +fix: make sure `getPlatformProxy`'s `ctx` methods throw illegal invocation errors like workerd + +in workerd detaching the `waitUntil` and `passThroughOnException` methods from the `ExecutionContext` +object results in them throwing `illegal invocation` errors, such as for example: + +```js +export default { + async fetch(_request, _env, { waitUntil }) { + waitUntil(() => {}); // <-- throws an illegal invocation error + return new Response("Hello World!"); + }, +}; +``` + +make sure that the same behavior is applied to the `ctx` object returned by `getPlatformProxy` diff --git a/fixtures/get-platform-proxy/tests/get-platform-proxy.ctx.test.ts b/fixtures/get-platform-proxy/tests/get-platform-proxy.ctx.test.ts index 731546e560a1..481ec7eece50 100644 --- a/fixtures/get-platform-proxy/tests/get-platform-proxy.ctx.test.ts +++ b/fixtures/get-platform-proxy/tests/get-platform-proxy.ctx.test.ts @@ -52,4 +52,86 @@ describe("getPlatformProxy - ctx", () => { await dispose(); } }); + + describe("detached methods should behave like workerd", () => { + it("destructured methods should throw illegal invocation errors", async () => { + const { + ctx: { waitUntil, passThroughOnException }, + dispose, + } = await getPlatformProxy(); + try { + expect(() => { + waitUntil(() => {}); + }).toThrowError("Illegal invocation"); + + expect(() => { + passThroughOnException(); + }).toThrowError("Illegal invocation"); + } finally { + await dispose(); + } + }); + + it("extracted methods should throw illegal invocation errors", async () => { + const { ctx, dispose } = await getPlatformProxy(); + const waitUntil = ctx.waitUntil; + const passThroughOnException = ctx.passThroughOnException; + + try { + expect(() => { + waitUntil(() => {}); + }).toThrowError("Illegal invocation"); + + expect(() => { + passThroughOnException(); + }).toThrowError("Illegal invocation"); + } finally { + await dispose(); + } + }); + + it("extracted methods which correctly bind this should not throw illegal invocation errors", async () => { + const { ctx, dispose } = await getPlatformProxy(); + const waitUntil = ctx.waitUntil.bind(ctx); + const passThroughOnException = ctx.passThroughOnException; + + try { + expect(() => { + waitUntil(() => {}); + }).not.toThrowError("Illegal invocation"); + + expect(() => { + passThroughOnException.apply(ctx, []); + }).not.toThrowError("Illegal invocation"); + + expect(() => { + passThroughOnException.call(ctx); + }).not.toThrowError("Illegal invocation"); + } finally { + await dispose(); + } + }); + + it("extracted methods which incorrectly bind this should throw illegal invocation errors", async () => { + const { ctx, dispose } = await getPlatformProxy(); + const waitUntil = ctx.waitUntil.bind({}); + const passThroughOnException = ctx.passThroughOnException; + + try { + expect(() => { + waitUntil(() => {}); + }).toThrowError("Illegal invocation"); + + expect(() => { + passThroughOnException.apply(5, []); + }).toThrowError("Illegal invocation"); + + expect(() => { + passThroughOnException.call(new Boolean(), []); + }).toThrowError("Illegal invocation"); + } finally { + await dispose(); + } + }); + }); }); diff --git a/packages/wrangler/src/api/integrations/platform/executionContext.ts b/packages/wrangler/src/api/integrations/platform/executionContext.ts index e940c026a8f2..6e847437359f 100644 --- a/packages/wrangler/src/api/integrations/platform/executionContext.ts +++ b/packages/wrangler/src/api/integrations/platform/executionContext.ts @@ -1,5 +1,13 @@ export class ExecutionContext { // eslint-disable-next-line @typescript-eslint/no-explicit-any, unused-imports/no-unused-vars - waitUntil(promise: Promise): void {} - passThroughOnException(): void {} + waitUntil(promise: Promise): void { + if (!(this instanceof ExecutionContext)) { + throw new Error("Illegal invocation"); + } + } + passThroughOnException(): void { + if (!(this instanceof ExecutionContext)) { + throw new Error("Illegal invocation"); + } + } }