( accessToken: string, config: GmailWebhookConfig, requestId: string, logger: Logger )
| 293 | } |
| 294 | |
| 295 | async function searchEmails( |
| 296 | accessToken: string, |
| 297 | config: GmailWebhookConfig, |
| 298 | requestId: string, |
| 299 | logger: Logger |
| 300 | ) { |
| 301 | try { |
| 302 | const baseQuery = buildGmailSearchQuery(config) |
| 303 | let timeConstraint = '' |
| 304 | |
| 305 | if (config.lastCheckedTimestamp) { |
| 306 | const lastCheckedTime = new Date(config.lastCheckedTimestamp) |
| 307 | const now = new Date() |
| 308 | const minutesSinceLastCheck = (now.getTime() - lastCheckedTime.getTime()) / (60 * 1000) |
| 309 | |
| 310 | if (minutesSinceLastCheck < 60) { |
| 311 | const bufferSeconds = Math.max(1 * 60 * 2, 180) |
| 312 | const cutoffTime = new Date(lastCheckedTime.getTime() - bufferSeconds * 1000) |
| 313 | const timestamp = Math.floor(cutoffTime.getTime() / 1000) |
| 314 | timeConstraint = ` after:${timestamp}` |
| 315 | } else if (minutesSinceLastCheck < 24 * 60) { |
| 316 | const hours = Math.ceil(minutesSinceLastCheck / 60) + 1 |
| 317 | timeConstraint = ` newer_than:${hours}h` |
| 318 | } else { |
| 319 | const days = Math.min(Math.ceil(minutesSinceLastCheck / (24 * 60)), 7) + 1 |
| 320 | timeConstraint = ` newer_than:${days}d` |
| 321 | } |
| 322 | } else { |
| 323 | timeConstraint = ' newer_than:1d' |
| 324 | } |
| 325 | |
| 326 | const query = `${baseQuery}${timeConstraint}` |
| 327 | const searchUrl = `https://gmail.googleapis.com/gmail/v1/users/me/messages?q=${encodeURIComponent(query)}&maxResults=${config.maxEmailsPerPoll || 25}` |
| 328 | |
| 329 | const searchResponse = await fetch(searchUrl, { |
| 330 | headers: { Authorization: `Bearer ${accessToken}` }, |
| 331 | }) |
| 332 | |
| 333 | if (!searchResponse.ok) { |
| 334 | const errorData = await searchResponse.json() |
| 335 | logger.error(`[${requestId}] Gmail search API error:`, { |
| 336 | status: searchResponse.status, |
| 337 | statusText: searchResponse.statusText, |
| 338 | query, |
| 339 | error: errorData, |
| 340 | }) |
| 341 | throw new Error( |
| 342 | `Gmail API error: ${searchResponse.status} ${searchResponse.statusText} - ${JSON.stringify(errorData)}` |
| 343 | ) |
| 344 | } |
| 345 | |
| 346 | const searchData = await searchResponse.json() |
| 347 | |
| 348 | if (!searchData.messages || !searchData.messages.length) { |
| 349 | logger.info(`[${requestId}] No emails found matching query: ${query}`) |
| 350 | return { emails: [], latestHistoryId: config.historyId } |
| 351 | } |
| 352 |
no test coverage detected