(error: unknown)
| 27 | const MAX_SURFACED_ERROR_LENGTH = 250 |
| 28 | |
| 29 | export function surfaceOauthError(error: unknown): string { |
| 30 | // Spec-compliant OAuth servers throw typed subclasses with clean RFC 6749 fields. |
| 31 | if (error instanceof OAuthError && !(error instanceof ServerError)) { |
| 32 | return truncate(`${error.errorCode}: ${error.message}`) |
| 33 | } |
| 34 | |
| 35 | // ServerError wraps non-spec response bodies as "HTTP N: Invalid OAuth error |
| 36 | // response: ... Raw body: {...}". Dig the vendor message out of the JSON tail. |
| 37 | if (error instanceof Error) { |
| 38 | const rawBodyMatch = error.message.match(/Raw body:\s*(\{[\s\S]*\})\s*$/) |
| 39 | if (rawBodyMatch) { |
| 40 | try { |
| 41 | const body = JSON.parse(rawBodyMatch[1]) as Record<string, unknown> |
| 42 | const vendorMessage = |
| 43 | (typeof body.error_description === 'string' && body.error_description) || |
| 44 | (typeof body.message === 'string' && body.message) || |
| 45 | (typeof body.error === 'string' && body.error) || |
| 46 | null |
| 47 | if (vendorMessage) return truncate(`Authorization server: ${vendorMessage}`) |
| 48 | } catch {} |
| 49 | } |
| 50 | return truncate(error.message.split('\n')[0] || 'Failed to start OAuth flow') |
| 51 | } |
| 52 | return 'Failed to start OAuth flow' |
| 53 | } |
| 54 | |
| 55 | function truncate(message: string): string { |
| 56 | return message.length > MAX_SURFACED_ERROR_LENGTH |
no test coverage detected