diff --git a/.changeset/api-logger-foreign-response.md b/.changeset/api-logger-foreign-response.md new file mode 100644 index 0000000000..e4a9e31491 --- /dev/null +++ b/.changeset/api-logger-foreign-response.md @@ -0,0 +1,5 @@ +--- +'e2b': patch +--- + +Fix every API call failing on Node with "onResponse: must return new Response() when modifying the response" when a `logger` is passed. The API logging middleware no longer returns the unchanged request and response, which openapi-fetch rejected because undici's `Response` is not the global one. diff --git a/packages/js-sdk/src/logs.ts b/packages/js-sdk/src/logs.ts index d3ed59f21f..d52076885b 100644 --- a/packages/js-sdk/src/logs.ts +++ b/packages/js-sdk/src/logs.ts @@ -58,11 +58,15 @@ export function createRpcLogger(logger: Logger): Interceptor { } } +/** + * The hooks return nothing: openapi-fetch treats a returned value as a + * replacement and requires it to be an instance of the global `Request` / + * `Response`, which undici's responses on Node are not. + */ export function createApiLogger(logger: Logger): Middleware { return { async onRequest({ request }) { logger.info?.(`Request ${request.method} ${request.url}`) - return request }, async onResponse({ response }) { if (response.status >= 400) { @@ -70,8 +74,6 @@ export function createApiLogger(logger: Logger): Middleware { } else { logger.info?.('Response:', response.status, response.statusText) } - - return response }, } } diff --git a/packages/js-sdk/tests/logs.test.ts b/packages/js-sdk/tests/logs.test.ts index e22dc3dac0..384b5fa179 100644 --- a/packages/js-sdk/tests/logs.test.ts +++ b/packages/js-sdk/tests/logs.test.ts @@ -1,5 +1,7 @@ +import createClient from 'openapi-fetch' +import { Response as UndiciResponse } from 'undici' import { assert, describe, test } from 'vitest' -import { createRpcLogger } from '../src/logs' +import { createApiLogger, createRpcLogger } from '../src/logs' const req = { url: 'http://localhost:49983/process.Process/Start' } as any @@ -37,3 +39,37 @@ describe('createRpcLogger', () => { ]) }) }) + +describe('createApiLogger', () => { + // On Node the API client fetches through undici, whose Response is not the + // global one; openapi-fetch rejects any onResponse result that is not an + // instance of the global Response. + test('logs responses that are not instances of the global Response', async () => { + assert.notStrictEqual(UndiciResponse, globalThis.Response) + const logs: any[][] = [] + const client = createClient({ + baseUrl: 'http://localhost', + fetch: async () => + new UndiciResponse('{"message":"boom"}', { + status: 500, + statusText: 'Internal Server Error', + headers: { 'content-type': 'application/json' }, + }) as unknown as Response, + }) + client.use( + createApiLogger({ + info: (...args) => logs.push(['info', ...args]), + error: (...args) => logs.push(['error', ...args]), + }) + ) + + const res = await client.GET('/sandboxes' as any) + + assert.equal(res.response.status, 500) + assert.deepEqual(res.error, { message: 'boom' }) + assert.deepEqual(logs, [ + ['info', 'Request GET http://localhost/sandboxes'], + ['error', 'Response:', 500, 'Internal Server Error'], + ]) + }) +})