diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 2022aed361..443c4d6a30 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.2.22 - name: Setup Node uses: actions/setup-node@v4 diff --git a/apps/sim/app/api/webhooks/[id]/route.ts b/apps/sim/app/api/webhooks/[id]/route.ts index 672d04a2d3..6561b4532f 100644 --- a/apps/sim/app/api/webhooks/[id]/route.ts +++ b/apps/sim/app/api/webhooks/[id]/route.ts @@ -282,11 +282,13 @@ export async function DELETE( if (!resolvedExternalId) { try { - const requestOrigin = new URL(request.url).origin - const effectiveOrigin = requestOrigin.includes('localhost') - ? env.NEXT_PUBLIC_APP_URL || requestOrigin - : requestOrigin - const expectedNotificationUrl = `${effectiveOrigin}/api/webhooks/trigger/${foundWebhook.path}` + if (!env.NEXT_PUBLIC_APP_URL) { + logger.error( + `[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot match Airtable webhook` + ) + throw new Error('NEXT_PUBLIC_APP_URL must be configured') + } + const expectedNotificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${foundWebhook.path}` const listUrl = `https://api.airtable.com/v0/bases/${baseId}/webhooks` const listResp = await fetch(listUrl, { diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index d0bca2da67..912406eced 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -432,25 +432,20 @@ async function createAirtableWebhookSubscription( logger.warn( `[${requestId}] Could not retrieve Airtable access token for user ${userId}. Cannot create webhook in Airtable.` ) - // Instead of silently returning, throw an error with clear user guidance throw new Error( 'Airtable account connection required. Please connect your Airtable account in the trigger configuration and try again.' ) } - const requestOrigin = new URL(request.url).origin - // Ensure origin does not point to localhost for external API calls - const effectiveOrigin = requestOrigin.includes('localhost') - ? env.NEXT_PUBLIC_APP_URL || requestOrigin // Use env var if available, fallback to original - : requestOrigin - - const notificationUrl = `${effectiveOrigin}/api/webhooks/trigger/${path}` - if (effectiveOrigin !== requestOrigin) { - logger.debug( - `[${requestId}] Remapped localhost origin to ${effectiveOrigin} for notificationUrl` + if (!env.NEXT_PUBLIC_APP_URL) { + logger.error( + `[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot register Airtable webhook` ) + throw new Error('NEXT_PUBLIC_APP_URL must be configured for Airtable webhook registration') } + const notificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${path}` + const airtableApiUrl = `https://api.airtable.com/v0/bases/${baseId}/webhooks` const specification: any = { @@ -549,19 +544,15 @@ async function createTelegramWebhookSubscription( return // Cannot proceed without botToken } - const requestOrigin = new URL(request.url).origin - // Ensure origin does not point to localhost for external API calls - const effectiveOrigin = requestOrigin.includes('localhost') - ? env.NEXT_PUBLIC_APP_URL || requestOrigin // Use env var if available, fallback to original - : requestOrigin - - const notificationUrl = `${effectiveOrigin}/api/webhooks/trigger/${path}` - if (effectiveOrigin !== requestOrigin) { - logger.debug( - `[${requestId}] Remapped localhost origin to ${effectiveOrigin} for notificationUrl` + if (!env.NEXT_PUBLIC_APP_URL) { + logger.error( + `[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot register Telegram webhook` ) + throw new Error('NEXT_PUBLIC_APP_URL must be configured for Telegram webhook registration') } + const notificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${path}` + const telegramApiUrl = `https://api.telegram.org/bot${botToken}/setWebhook` const requestBody: any = { diff --git a/apps/sim/app/api/webhooks/test/route.ts b/apps/sim/app/api/webhooks/test/route.ts index 3bd05846c8..5b8886bf23 100644 --- a/apps/sim/app/api/webhooks/test/route.ts +++ b/apps/sim/app/api/webhooks/test/route.ts @@ -2,6 +2,7 @@ import { db } from '@sim/db' import { webhook } from '@sim/db/schema' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' +import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { generateRequestId } from '@/lib/utils' @@ -13,7 +14,6 @@ export async function GET(request: NextRequest) { const requestId = generateRequestId() try { - // Get the webhook ID and provider from the query parameters const { searchParams } = new URL(request.url) const webhookId = searchParams.get('id') @@ -24,7 +24,6 @@ export async function GET(request: NextRequest) { logger.debug(`[${requestId}] Testing webhook with ID: ${webhookId}`) - // Find the webhook in the database const webhooks = await db.select().from(webhook).where(eq(webhook.id, webhookId)).limit(1) if (webhooks.length === 0) { @@ -36,8 +35,14 @@ export async function GET(request: NextRequest) { const provider = foundWebhook.provider || 'generic' const providerConfig = (foundWebhook.providerConfig as Record) || {} - // Construct the webhook URL - const baseUrl = new URL(request.url).origin + if (!env.NEXT_PUBLIC_APP_URL) { + logger.error(`[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot test webhook`) + return NextResponse.json( + { success: false, error: 'NEXT_PUBLIC_APP_URL must be configured' }, + { status: 500 } + ) + } + const baseUrl = env.NEXT_PUBLIC_APP_URL const webhookUrl = `${baseUrl}/api/webhooks/trigger/${foundWebhook.path}` logger.info(`[${requestId}] Testing webhook for provider: ${provider}`, { @@ -46,7 +51,6 @@ export async function GET(request: NextRequest) { isActive: foundWebhook.isActive, }) - // Provider-specific test logic switch (provider) { case 'whatsapp': { const verificationToken = providerConfig.verificationToken @@ -59,10 +63,8 @@ export async function GET(request: NextRequest) { ) } - // Generate a test challenge const challenge = `test_${Date.now()}` - // Construct the WhatsApp verification URL const whatsappUrl = `${webhookUrl}?hub.mode=subscribe&hub.verify_token=${verificationToken}&hub.challenge=${challenge}` logger.debug(`[${requestId}] Testing WhatsApp webhook verification`, { @@ -70,19 +72,16 @@ export async function GET(request: NextRequest) { challenge, }) - // Make a request to the webhook endpoint const response = await fetch(whatsappUrl, { headers: { 'User-Agent': 'facebookplatform/1.0', }, }) - // Get the response details const status = response.status const contentType = response.headers.get('content-type') const responseText = await response.text() - // Check if the test was successful const success = status === 200 && responseText === challenge if (success) { @@ -139,7 +138,6 @@ export async function GET(request: NextRequest) { ) } - // Test the webhook endpoint with a simple message to check if it's reachable const testMessage = { update_id: 12345, message: { @@ -165,7 +163,6 @@ export async function GET(request: NextRequest) { url: webhookUrl, }) - // Make a test request to the webhook endpoint const response = await fetch(webhookUrl, { method: 'POST', headers: { @@ -175,16 +172,12 @@ export async function GET(request: NextRequest) { body: JSON.stringify(testMessage), }) - // Get the response details const status = response.status let responseText = '' try { responseText = await response.text() - } catch (_e) { - // Ignore if we can't get response text - } + } catch (_e) {} - // Consider success if we get a 2xx response const success = status >= 200 && status < 300 if (success) { @@ -196,7 +189,6 @@ export async function GET(request: NextRequest) { }) } - // Get webhook info from Telegram API let webhookInfo = null try { const webhookInfoUrl = `https://api.telegram.org/bot${botToken}/getWebhookInfo` @@ -215,7 +207,6 @@ export async function GET(request: NextRequest) { logger.warn(`[${requestId}] Failed to get Telegram webhook info`, e) } - // Format the curl command for testing const curlCommand = [ `curl -X POST "${webhookUrl}"`, `-H "Content-Type: application/json"`, @@ -288,16 +279,13 @@ export async function GET(request: NextRequest) { } case 'generic': { - // Get the general webhook configuration const token = providerConfig.token const secretHeaderName = providerConfig.secretHeaderName const requireAuth = providerConfig.requireAuth const allowedIps = providerConfig.allowedIps - // Generate sample curl command for testing let curlCommand = `curl -X POST "${webhookUrl}" -H "Content-Type: application/json"` - // Add auth headers to the curl command if required if (requireAuth && token) { if (secretHeaderName) { curlCommand += ` -H "${secretHeaderName}: ${token}"` @@ -306,7 +294,6 @@ export async function GET(request: NextRequest) { } } - // Add a sample payload curlCommand += ` -d '{"event":"test_event","timestamp":"${new Date().toISOString()}"}'` logger.info(`[${requestId}] General webhook test successful: ${webhookId}`) @@ -391,7 +378,6 @@ export async function GET(request: NextRequest) { }) } - // Add the Airtable test case case 'airtable': { const baseId = providerConfig.baseId const tableId = providerConfig.tableId @@ -408,7 +394,6 @@ export async function GET(request: NextRequest) { ) } - // Define a sample payload structure const samplePayload = { webhook: { id: 'whiYOUR_WEBHOOK_ID', @@ -418,16 +403,15 @@ export async function GET(request: NextRequest) { }, payloadFormat: 'v0', actionMetadata: { - source: 'tableOrViewChange', // Example source + source: 'tableOrViewChange', sourceMetadata: {}, }, payloads: [ { timestamp: new Date().toISOString(), - baseTransactionNumber: Date.now(), // Example transaction number + baseTransactionNumber: Date.now(), changedTablesById: { [tableId]: { - // Example changes - structure may vary based on actual event changedRecordsById: { recSAMPLEID1: { current: { cellValuesByFieldId: { fldSAMPLEID: 'New Value' } }, @@ -442,7 +426,6 @@ export async function GET(request: NextRequest) { ], } - // Generate sample curl command let curlCommand = `curl -X POST "${webhookUrl}" -H "Content-Type: application/json"` curlCommand += ` -d '${JSON.stringify(samplePayload, null, 2)}'` @@ -519,7 +502,6 @@ export async function GET(request: NextRequest) { } default: { - // Generic webhook test logger.info(`[${requestId}] Generic webhook test successful: ${webhookId}`) return NextResponse.json({ success: true, diff --git a/docker/app.Dockerfile b/docker/app.Dockerfile index a6d012f4ca..eb0e666fb8 100644 --- a/docker/app.Dockerfile +++ b/docker/app.Dockerfile @@ -1,7 +1,7 @@ # ======================================== # Base Stage: Alpine Linux with Bun # ======================================== -FROM oven/bun:alpine AS base +FROM oven/bun:1.2.22-alpine AS base # ======================================== # Dependencies Stage: Install Dependencies diff --git a/docker/db.Dockerfile b/docker/db.Dockerfile index 681cd138f4..40a9d47c86 100644 --- a/docker/db.Dockerfile +++ b/docker/db.Dockerfile @@ -1,7 +1,7 @@ # ======================================== # Dependencies Stage: Install Dependencies # ======================================== -FROM oven/bun:1.2.21-alpine AS deps +FROM oven/bun:1.2.22-alpine AS deps WORKDIR /app # Copy only package files needed for migrations @@ -14,7 +14,7 @@ RUN bun install --ignore-scripts # ======================================== # Runner Stage: Production Environment # ======================================== -FROM oven/bun:1.2.21-alpine AS runner +FROM oven/bun:1.2.22-alpine AS runner WORKDIR /app # Copy only the necessary files from deps diff --git a/docker/realtime.Dockerfile b/docker/realtime.Dockerfile index 1e7f9fa54b..7fb83bae12 100644 --- a/docker/realtime.Dockerfile +++ b/docker/realtime.Dockerfile @@ -1,7 +1,7 @@ # ======================================== # Base Stage: Alpine Linux with Bun # ======================================== -FROM oven/bun:alpine AS base +FROM oven/bun:1.2.22-alpine AS base # ======================================== # Dependencies Stage: Install Dependencies