* Make an authenticated request to the Slack API
(
method: string,
params: Record<string, string | number | boolean | undefined> = {},
retries = 3
)
| 63 | * Make an authenticated request to the Slack API |
| 64 | */ |
| 65 | async function slackRequest<T>( |
| 66 | method: string, |
| 67 | params: Record<string, string | number | boolean | undefined> = {}, |
| 68 | retries = 3 |
| 69 | ): Promise<T> { |
| 70 | if (!SLACK_BOT_TOKEN) { |
| 71 | throw new Error('ADDIE_BOT_TOKEN is not configured'); |
| 72 | } |
| 73 | |
| 74 | const url = new URL(`${SLACK_API_BASE}/${method}`); |
| 75 | |
| 76 | // Add params to URL for GET requests (most Slack API methods use this) |
| 77 | Object.entries(params).forEach(([key, value]) => { |
| 78 | if (value !== undefined) { |
| 79 | url.searchParams.set(key, String(value)); |
| 80 | } |
| 81 | }); |
| 82 | |
| 83 | for (let attempt = 1; attempt <= retries; attempt++) { |
| 84 | try { |
| 85 | const response = await fetch(url.toString(), { |
| 86 | method: 'GET', |
| 87 | headers: { |
| 88 | Authorization: `Bearer ${SLACK_BOT_TOKEN}`, |
| 89 | 'Content-Type': 'application/x-www-form-urlencoded', |
| 90 | }, |
| 91 | }); |
| 92 | |
| 93 | const data = (await response.json()) as T & { ok: boolean; error?: string }; |
| 94 | |
| 95 | if (!data.ok) { |
| 96 | // Handle rate limiting |
| 97 | if (data.error === 'ratelimited') { |
| 98 | const retryAfter = response.headers.get('Retry-After'); |
| 99 | const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 60000; |
| 100 | logger.warn({ method, delay }, 'Slack rate limited, waiting'); |
| 101 | await sleep(delay); |
| 102 | continue; |
| 103 | } |
| 104 | |
| 105 | throw new Error(`Slack API error: ${data.error}`); |
| 106 | } |
| 107 | |
| 108 | return data; |
| 109 | } catch (error) { |
| 110 | // Don't retry permanent Slack API errors |
| 111 | if (error instanceof Error && SLACK_PERMANENT_ERRORS.some(e => error.message.includes(e))) { |
| 112 | logger.warn({ error: error.message, method }, 'Slack API permanent error'); |
| 113 | throw error; |
| 114 | } |
| 115 | |
| 116 | logger.warn({ error, method, attempt, retries }, 'Slack API request failed'); |
| 117 | |
| 118 | if (attempt === retries) { |
| 119 | throw error; |
| 120 | } |
| 121 | |
| 122 | // Exponential backoff |