* Make a POST request to the Slack API (for chat.postMessage, etc.)
( method: string, body: Record<string, unknown>, retries = 3 )
| 132 | * Make a POST request to the Slack API (for chat.postMessage, etc.) |
| 133 | */ |
| 134 | async function slackPostRequest<T>( |
| 135 | method: string, |
| 136 | body: Record<string, unknown>, |
| 137 | retries = 3 |
| 138 | ): Promise<T> { |
| 139 | if (!SLACK_BOT_TOKEN) { |
| 140 | throw new Error('ADDIE_BOT_TOKEN is not configured'); |
| 141 | } |
| 142 | |
| 143 | const url = `${SLACK_API_BASE}/${method}`; |
| 144 | |
| 145 | for (let attempt = 1; attempt <= retries; attempt++) { |
| 146 | try { |
| 147 | const response = await fetch(url, { |
| 148 | method: 'POST', |
| 149 | headers: { |
| 150 | Authorization: `Bearer ${SLACK_BOT_TOKEN}`, |
| 151 | 'Content-Type': 'application/json; charset=utf-8', |
| 152 | }, |
| 153 | body: JSON.stringify(body), |
| 154 | }); |
| 155 | |
| 156 | const data = (await response.json()) as T & { ok: boolean; error?: string }; |
| 157 | |
| 158 | if (!data.ok) { |
| 159 | if (data.error === 'ratelimited') { |
| 160 | const retryAfter = response.headers.get('Retry-After'); |
| 161 | const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 60000; |
| 162 | logger.warn({ method, delay }, 'Slack rate limited, waiting'); |
| 163 | await sleep(delay); |
| 164 | continue; |
| 165 | } |
| 166 | |
| 167 | throw new Error(`Slack API error: ${data.error}`); |
| 168 | } |
| 169 | |
| 170 | return data; |
| 171 | } catch (error) { |
| 172 | // Don't retry permanent Slack API errors |
| 173 | if (error instanceof Error && SLACK_PERMANENT_ERRORS.some(e => error.message.includes(e))) { |
| 174 | logger.warn({ error: error.message, method }, 'Slack API permanent error'); |
| 175 | throw error; |
| 176 | } |
| 177 | |
| 178 | logger.warn({ error, method, attempt, retries }, 'Slack POST request failed'); |
| 179 | |
| 180 | if (attempt === retries) { |
| 181 | throw error; |
| 182 | } |
| 183 | |
| 184 | const delay = Math.pow(2, attempt) * 1000; |
| 185 | await sleep(delay); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | throw new Error(`Slack POST request failed after ${retries} retries`); |
| 190 | } |
| 191 |