Skip to content

Commit 086b7d9

Browse files
waleedlatif1claude
andauthored
refactor(polling): consolidate polling services into provider handler pattern (#4035)
* refactor(polling): consolidate polling services into provider handler pattern Eliminate self-POST anti-pattern and extract shared boilerplate from 4 polling services into a clean handler registry mirroring lib/webhooks/providers/. - Add processPolledWebhookEvent() to processor.ts for direct in-process webhook execution, removing HTTP round-trips that caused Lambda 403/timeout errors - Extract shared utilities (markWebhookFailed/Success, fetchActiveWebhooks, runWithConcurrency, resolveOAuthCredential, updateWebhookProviderConfig) - Create PollingProviderHandler interface with per-provider implementations - Consolidate 4 identical route files into single dynamic [provider] route - Standardize concurrency to 10 across all providers - No infra changes needed — Helm cron paths resolve via dynamic route Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * polish(polling): extract lock TTL constant and remove unnecessary type casts - Widen processPolledWebhookEvent body param to accept object, eliminating `as unknown as Record<string, unknown>` double casts in all 4 handlers - Extract LOCK_TTL_SECONDS constant in route, tying maxDuration and lock TTL to a single value Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(polling): address PR review feedback - Add archivedAt filters to fetchActiveWebhooks query, matching findWebhookAndWorkflow in processor.ts to prevent polling archived webhooks/workflows - Move provider validation after auth check to prevent provider enumeration by unauthenticated callers - Fix inconsistent pollingIdempotency import path in outlook.ts to match other handlers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(polling): use literal for maxDuration segment config Next.js requires segment config exports to be statically analyzable literals. Using a variable reference caused build failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2760b4b commit 086b7d9

File tree

16 files changed

+1701
-2097
lines changed

16 files changed

+1701
-2097
lines changed

apps/sim/app/api/webhooks/poll/rss/route.ts renamed to apps/sim/app/api/webhooks/poll/[provider]/route.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,36 @@ import { type NextRequest, NextResponse } from 'next/server'
33
import { verifyCronAuth } from '@/lib/auth/internal'
44
import { acquireLock, releaseLock } from '@/lib/core/config/redis'
55
import { generateShortId } from '@/lib/core/utils/uuid'
6-
import { pollRssWebhooks } from '@/lib/webhooks/rss-polling-service'
6+
import { pollProvider, VALID_POLLING_PROVIDERS } from '@/lib/webhooks/polling'
77

8-
const logger = createLogger('RssPollingAPI')
8+
const logger = createLogger('PollingAPI')
99

10-
export const dynamic = 'force-dynamic'
11-
export const maxDuration = 180 // Allow up to 3 minutes for polling to complete
10+
/** Lock TTL in seconds — must match maxDuration so the lock auto-expires if the function times out. */
11+
const LOCK_TTL_SECONDS = 180
1212

13-
const LOCK_KEY = 'rss-polling-lock'
14-
const LOCK_TTL_SECONDS = 180 // Same as maxDuration (3 min)
13+
export const dynamic = 'force-dynamic'
14+
export const maxDuration = 180
1515

16-
export async function GET(request: NextRequest) {
16+
export async function GET(
17+
request: NextRequest,
18+
{ params }: { params: Promise<{ provider: string }> }
19+
) {
20+
const { provider } = await params
1721
const requestId = generateShortId()
18-
logger.info(`RSS webhook polling triggered (${requestId})`)
1922

23+
const LOCK_KEY = `${provider}-polling-lock`
2024
let lockValue: string | undefined
2125

2226
try {
23-
const authError = verifyCronAuth(request, 'RSS webhook polling')
24-
if (authError) {
25-
return authError
27+
const authError = verifyCronAuth(request, `${provider} webhook polling`)
28+
if (authError) return authError
29+
30+
if (!VALID_POLLING_PROVIDERS.has(provider)) {
31+
return NextResponse.json({ error: `Unknown polling provider: ${provider}` }, { status: 404 })
2632
}
2733

2834
lockValue = requestId
2935
const locked = await acquireLock(LOCK_KEY, lockValue, LOCK_TTL_SECONDS)
30-
3136
if (!locked) {
3237
return NextResponse.json(
3338
{
@@ -40,21 +45,21 @@ export async function GET(request: NextRequest) {
4045
)
4146
}
4247

43-
const results = await pollRssWebhooks()
48+
const results = await pollProvider(provider)
4449

4550
return NextResponse.json({
4651
success: true,
47-
message: 'RSS polling completed',
52+
message: `${provider} polling completed`,
4853
requestId,
4954
status: 'completed',
5055
...results,
5156
})
5257
} catch (error) {
53-
logger.error(`Error during RSS polling (${requestId}):`, error)
58+
logger.error(`Error during ${provider} polling (${requestId}):`, error)
5459
return NextResponse.json(
5560
{
5661
success: false,
57-
message: 'RSS polling failed',
62+
message: `${provider} polling failed`,
5863
error: error instanceof Error ? error.message : 'Unknown error',
5964
requestId,
6065
},

apps/sim/app/api/webhooks/poll/gmail/route.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.

apps/sim/app/api/webhooks/poll/imap/route.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.

apps/sim/app/api/webhooks/poll/outlook/route.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.

0 commit comments

Comments
 (0)