(ctx: PollWebhookContext)
| 95 | label: 'Google Calendar', |
| 96 | |
| 97 | async pollWebhook(ctx: PollWebhookContext): Promise<'success' | 'failure'> { |
| 98 | const { webhookData, workflowData, requestId, logger } = ctx |
| 99 | const webhookId = webhookData.id |
| 100 | |
| 101 | try { |
| 102 | const accessToken = await resolveOAuthCredential( |
| 103 | webhookData, |
| 104 | 'google-calendar', |
| 105 | requestId, |
| 106 | logger |
| 107 | ) |
| 108 | |
| 109 | const config = getProviderConfig<GoogleCalendarWebhookConfig>(webhookData.providerConfig) |
| 110 | // Canonical key `calendarId` first; `manualCalendarId` is a transitional basic-first |
| 111 | // fallback. Defaults to the user's primary calendar when none is set. |
| 112 | const calendarId = |
| 113 | readCanonicalTriggerValue(config.calendarId, config.manualCalendarId) || 'primary' |
| 114 | |
| 115 | // First poll: seed timestamp, emit nothing |
| 116 | if (!config.lastCheckedTimestamp) { |
| 117 | await updateWebhookProviderConfig( |
| 118 | webhookId, |
| 119 | { lastCheckedTimestamp: new Date(Date.now() - 30_000).toISOString() }, |
| 120 | logger |
| 121 | ) |
| 122 | await markWebhookSuccess(webhookId, logger) |
| 123 | logger.info(`[${requestId}] First poll for webhook ${webhookId}, seeded timestamp`) |
| 124 | return 'success' |
| 125 | } |
| 126 | |
| 127 | // Fetch changed events since last poll |
| 128 | const events = await fetchChangedEvents(accessToken, calendarId, config, requestId, logger) |
| 129 | |
| 130 | if (!events.length) { |
| 131 | await markWebhookSuccess(webhookId, logger) |
| 132 | logger.info(`[${requestId}] No changed events for webhook ${webhookId}`) |
| 133 | return 'success' |
| 134 | } |
| 135 | |
| 136 | logger.info(`[${requestId}] Found ${events.length} changed events for webhook ${webhookId}`) |
| 137 | |
| 138 | const { processedCount, failedCount, latestUpdated } = await processEvents( |
| 139 | events, |
| 140 | calendarId, |
| 141 | config.eventTypeFilter, |
| 142 | webhookData, |
| 143 | workflowData, |
| 144 | requestId, |
| 145 | logger |
| 146 | ) |
| 147 | |
| 148 | // Advance cursor to latestUpdated - 5s for clock-skew overlap, but never regress |
| 149 | // below the previous cursor — this prevents an infinite re-fetch loop when all |
| 150 | // returned events are filtered client-side and latestUpdated is within 5s of the cursor. |
| 151 | const newTimestamp = |
| 152 | failedCount > 0 |
| 153 | ? config.lastCheckedTimestamp |
| 154 | : latestUpdated |
nothing calls this directly
no test coverage detected