( accessToken: string, config: GmailWebhookConfig, requestId: string, logger: Logger )
| 146 | } |
| 147 | |
| 148 | async function fetchNewEmails( |
| 149 | accessToken: string, |
| 150 | config: GmailWebhookConfig, |
| 151 | requestId: string, |
| 152 | logger: Logger |
| 153 | ) { |
| 154 | try { |
| 155 | const useHistoryApi = !!config.historyId |
| 156 | let emails: GmailEmail[] = [] |
| 157 | let latestHistoryId = config.historyId |
| 158 | |
| 159 | if (useHistoryApi) { |
| 160 | const messageIds = new Set<string>() |
| 161 | let pageToken: string | undefined |
| 162 | |
| 163 | do { |
| 164 | let historyUrl = `https://gmail.googleapis.com/gmail/v1/users/me/history?startHistoryId=${config.historyId}&historyTypes=messageAdded` |
| 165 | if (pageToken) { |
| 166 | historyUrl += `&pageToken=${pageToken}` |
| 167 | } |
| 168 | |
| 169 | const historyResponse = await fetch(historyUrl, { |
| 170 | headers: { Authorization: `Bearer ${accessToken}` }, |
| 171 | }) |
| 172 | |
| 173 | if (!historyResponse.ok) { |
| 174 | const status = historyResponse.status |
| 175 | const errorData = await historyResponse.json().catch(() => ({})) |
| 176 | logger.error(`[${requestId}] Gmail history API error:`, { |
| 177 | status, |
| 178 | statusText: historyResponse.statusText, |
| 179 | error: errorData, |
| 180 | }) |
| 181 | |
| 182 | if (status === 403 || status === 429) { |
| 183 | throw new Error( |
| 184 | `Gmail API error ${status} — skipping to retry next poll cycle: ${JSON.stringify(errorData)}` |
| 185 | ) |
| 186 | } |
| 187 | |
| 188 | logger.info(`[${requestId}] Falling back to search API after history API error ${status}`) |
| 189 | const searchResult = await searchEmails(accessToken, config, requestId, logger) |
| 190 | if (searchResult.emails.length === 0) { |
| 191 | const freshHistoryId = await getGmailProfileHistoryId(accessToken, requestId, logger) |
| 192 | if (freshHistoryId) { |
| 193 | logger.info( |
| 194 | `[${requestId}] Fetched fresh historyId ${freshHistoryId} after invalid historyId (was: ${config.historyId})` |
| 195 | ) |
| 196 | return { emails: [], latestHistoryId: freshHistoryId } |
| 197 | } |
| 198 | } |
| 199 | return searchResult |
| 200 | } |
| 201 | |
| 202 | const historyData = await historyResponse.json() |
| 203 | |
| 204 | if (historyData.historyId) { |
| 205 | latestHistoryId = historyData.historyId |
no test coverage detected