diff --git a/apps/sim/app/api/proxy/route.ts b/apps/sim/app/api/proxy/route.ts index f7a116625e..ec67bbc0ef 100644 --- a/apps/sim/app/api/proxy/route.ts +++ b/apps/sim/app/api/proxy/route.ts @@ -71,28 +71,43 @@ export async function GET(request: Request) { return createErrorResponse("Missing 'url' parameter", 400) } - // Extract custom headers from the request + const method = url.searchParams.get('method') || 'GET' + + const bodyParam = url.searchParams.get('body') + let body: string | undefined = undefined + + if (bodyParam && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) { + try { + body = decodeURIComponent(bodyParam) + } catch (error) { + logger.warn(`[${requestId}] Failed to decode body parameter`, error) + } + } + const customHeaders: Record = {} - // Process all header.* parameters in the URL for (const [key, value] of url.searchParams.entries()) { if (key.startsWith('header.')) { - const headerName = key.substring(7) // Remove 'header.' prefix + const headerName = key.substring(7) customHeaders[headerName] = value } } - logger.info(`[${requestId}] Proxying GET request to: ${targetUrl}`) - logger.debug(`[${requestId}] Custom headers:`, customHeaders) + if (body && !customHeaders['Content-Type']) { + customHeaders['Content-Type'] = 'application/json' + } + + logger.info(`[${requestId}] Proxying ${method} request to: ${targetUrl}`) try { // Forward the request to the target URL with all specified headers const response = await fetch(targetUrl, { - method: 'GET', + method: method, headers: { ...getProxyHeaders(), ...customHeaders, }, + body: body || undefined, }) // Get response data diff --git a/apps/sim/tools/http/request.test.ts b/apps/sim/tools/http/request.test.ts index 73aa47b736..561efd0401 100644 --- a/apps/sim/tools/http/request.test.ts +++ b/apps/sim/tools/http/request.test.ts @@ -452,5 +452,75 @@ describe('HTTP Request Tool', () => { // Reset window global.window = originalWindow }) + + test('should include method parameter in proxy URL', () => { + const originalWindow = global.window + Object.defineProperty(global, 'window', { + value: { + location: { + origin: 'https://simstudio.ai', + }, + }, + writable: true, + }) + + const originalVitest = process.env.VITEST + + try { + process.env.VITEST = undefined + + const buildProxyUrl = (params: any) => { + const baseUrl = `https://external-api.com/endpoint` + let proxyUrl = `/api/proxy?url=${encodeURIComponent(baseUrl)}` + + if (params.method) { + proxyUrl += `&method=${encodeURIComponent(params.method)}` + } + + if ( + params.body && + ['POST', 'PUT', 'PATCH'].includes(params.method?.toUpperCase() || '') + ) { + const bodyStr = + typeof params.body === 'string' ? params.body : JSON.stringify(params.body) + proxyUrl += `&body=${encodeURIComponent(bodyStr)}` + } + + return proxyUrl + } + + const getParams = { + url: 'https://external-api.com/endpoint', + method: 'GET', + } + const getProxyUrl = buildProxyUrl(getParams) + expect(getProxyUrl).toContain('/api/proxy?url=') + expect(getProxyUrl).toContain('&method=GET') + + const postParams = { + url: 'https://external-api.com/endpoint', + method: 'POST', + body: { key: 'value' }, + } + const postProxyUrl = buildProxyUrl(postParams) + expect(postProxyUrl).toContain('/api/proxy?url=') + expect(postProxyUrl).toContain('&method=POST') + expect(postProxyUrl).toContain('&body=') + expect(postProxyUrl).toContain(encodeURIComponent('{"key":"value"}')) + + const putParams = { + url: 'https://external-api.com/endpoint', + method: 'PUT', + body: 'string body', + } + const putProxyUrl = buildProxyUrl(putParams) + expect(putProxyUrl).toContain('/api/proxy?url=') + expect(putProxyUrl).toContain('&method=PUT') + expect(putProxyUrl).toContain(`&body=${encodeURIComponent('string body')}`) + } finally { + global.window = originalWindow + process.env.VITEST = originalVitest + } + }) }) }) diff --git a/apps/sim/tools/http/request.ts b/apps/sim/tools/http/request.ts index 0a6578b833..9edc909aa3 100644 --- a/apps/sim/tools/http/request.ts +++ b/apps/sim/tools/http/request.ts @@ -232,9 +232,18 @@ export const requestTool: ToolConfig = { // Determine if we should use the proxy if (shouldUseProxy(url)) { - // Route request through our proxy let proxyUrl = `/api/proxy?url=${encodeURIComponent(url)}` + if (params.method) { + proxyUrl += `&method=${encodeURIComponent(params.method)}` + } + + if (params.body && ['POST', 'PUT', 'PATCH'].includes(params.method?.toUpperCase() || '')) { + const bodyStr = + typeof params.body === 'string' ? params.body : JSON.stringify(params.body) + proxyUrl += `&body=${encodeURIComponent(bodyStr)}` + } + // Forward all headers as URL parameters const userHeaders = transformTable(params.headers || null) @@ -387,6 +396,16 @@ export const requestTool: ToolConfig = { if (shouldUseProxy(processedUrl)) { let proxyUrl = `/api/proxy?url=${encodeURIComponent(processedUrl)}` + if (params.method) { + proxyUrl += `&method=${encodeURIComponent(params.method)}` + } + + if (params.body && ['POST', 'PUT', 'PATCH'].includes(params.method?.toUpperCase() || '')) { + const bodyStr = + typeof params.body === 'string' ? params.body : JSON.stringify(params.body) + proxyUrl += `&body=${encodeURIComponent(bodyStr)}` + } + // Forward all headers as URL parameters const userHeaders = transformTable(params.headers || null) for (const [key, value] of Object.entries(userHeaders)) {