({ body, contentType, metadata, signal })
| 181 | let resolvedIP: string | null = null |
| 182 | return { |
| 183 | async deliver({ body, contentType, metadata, signal }) { |
| 184 | // Resolve once per session and pin across retries to defeat DNS rebinding (TOCTOU). |
| 185 | if (resolvedIP === null) { |
| 186 | resolvedIP = await resolvePublicTarget(config.url) |
| 187 | } |
| 188 | let lastError: unknown |
| 189 | for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { |
| 190 | if (signal.aborted) throw signal.reason ?? new Error('Aborted') |
| 191 | const headers = buildHeaders({ config, credentials, body, contentType, metadata }) |
| 192 | let retryAfterMs: number | null = null |
| 193 | let response: Awaited<ReturnType<typeof secureFetchWithPinnedIP>> | undefined |
| 194 | try { |
| 195 | response = await secureFetchWithPinnedIP(config.url, resolvedIP, { |
| 196 | method: 'POST', |
| 197 | body: new Uint8Array(body), |
| 198 | headers, |
| 199 | signal, |
| 200 | timeout: PER_ATTEMPT_TIMEOUT_MS, |
| 201 | maxResponseBytes: MAX_RESPONSE_BYTES, |
| 202 | }) |
| 203 | } catch (error) { |
| 204 | lastError = error |
| 205 | logger.debug('Webhook delivery attempt failed', { |
| 206 | url: config.url, |
| 207 | attempt, |
| 208 | error: toError(error).message, |
| 209 | }) |
| 210 | } |
| 211 | if (response) { |
| 212 | if (response.ok) { |
| 213 | const requestId = |
| 214 | response.headers.get('x-request-id') ?? |
| 215 | response.headers.get('x-amzn-trace-id') ?? |
| 216 | null |
| 217 | logger.debug('Webhook chunk delivered', { |
| 218 | url: config.url, |
| 219 | attempt, |
| 220 | status: response.status, |
| 221 | bytes: body.byteLength, |
| 222 | }) |
| 223 | return { |
| 224 | locator: requestId |
| 225 | ? `${config.url}#${metadata.runId}-${metadata.sequence}@${requestId}` |
| 226 | : `${config.url}#${metadata.runId}-${metadata.sequence}`, |
| 227 | } |
| 228 | } |
| 229 | if (!isRetryableStatus(response.status)) { |
| 230 | throw new Error(`Webhook responded with HTTP ${response.status}`) |
| 231 | } |
| 232 | lastError = new Error(`Webhook responded with HTTP ${response.status}`) |
| 233 | retryAfterMs = parseRetryAfter(response.headers.get('retry-after')) |
| 234 | } |
| 235 | if (attempt < MAX_ATTEMPTS) { |
| 236 | await sleepUntilAborted(backoffWithJitter(attempt, retryAfterMs), signal) |
| 237 | } |
| 238 | } |
| 239 | throw lastError instanceof Error |
| 240 | ? lastError |
nothing calls this directly
no test coverage detected