Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions apps/sim/app/api/webhooks/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down
33 changes: 12 additions & 21 deletions apps/sim/app/api/webhooks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 = {
Expand Down
42 changes: 12 additions & 30 deletions apps/sim/app/api/webhooks/test/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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')

Expand All @@ -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) {
Expand All @@ -36,8 +35,14 @@ export async function GET(request: NextRequest) {
const provider = foundWebhook.provider || 'generic'
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}

// 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}`, {
Expand All @@ -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
Expand All @@ -59,30 +63,25 @@ 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`, {
webhookId,
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) {
Expand Down Expand Up @@ -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: {
Expand All @@ -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: {
Expand All @@ -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) {
Expand All @@ -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`
Expand All @@ -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"`,
Expand Down Expand Up @@ -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}"`
Expand All @@ -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}`)
Expand Down Expand Up @@ -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
Expand All @@ -408,7 +394,6 @@ export async function GET(request: NextRequest) {
)
}

// Define a sample payload structure
const samplePayload = {
webhook: {
id: 'whiYOUR_WEBHOOK_ID',
Expand All @@ -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' } },
Expand All @@ -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)}'`

Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion docker/app.Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions docker/db.Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docker/realtime.Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down