| ... | @@ -110,6 +110,40 @@ async function readErrorMessage(response: Response, fallbackMessage: string): Pr | ... | @@ -110,6 +110,40 @@ async function readErrorMessage(response: Response, fallbackMessage: string): Pr |
| 110 | return text || fallbackMessage; | 110 | return text || fallbackMessage; |
| 111 | } | 111 | } |
| 112 | | 112 | |
| | 113 | const INVALID_BOOTSTRAP_CREDENTIAL_MESSAGES = new Set([ |
| | 114 | "Invalid bootstrap credential.", |
| | 115 | "Unknown bootstrap credential.", |
| | 116 | ]); |
| | 117 | |
| | 118 | function parseBootstrapErrorMessage(message: string): string { |
| | 119 | const trimmed = message.trim(); |
| | 120 | if (trimmed.length === 0) { |
| | 121 | return ""; |
| | 122 | } |
| | 123 | |
| | 124 | try { |
| | 125 | const parsed = JSON.parse(trimmed) as { |
| | 126 | error?: unknown; |
| | 127 | }; |
| | 128 | if (typeof parsed.error === "string" && parsed.error.trim().length > 0) { |
| | 129 | return parsed.error.trim(); |
| | 130 | } |
| | 131 | } catch { |
| | 132 | // Not JSON; fall back to plain text. |
| | 133 | } |
| | 134 | |
| | 135 | return trimmed; |
| | 136 | } |
| | 137 | |
| | 138 | function toFriendlyBootstrapErrorMessage(status: number, message: string): string { |
| | 139 | const parsedMessage = parseBootstrapErrorMessage(message); |
| | 140 | if (status === 401 && INVALID_BOOTSTRAP_CREDENTIAL_MESSAGES.has(parsedMessage)) { |
| | 141 | return "Invalid pairing token. Check the token and try again."; |
| | 142 | } |
| | 143 | |
| | 144 | return parsedMessage; |
| | 145 | } |
| | 146 | |
| 113 | async function exchangeBootstrapCredential(credential: string): Promise<AuthBootstrapResult> { | 147 | async function exchangeBootstrapCredential(credential: string): Promise<AuthBootstrapResult> { |
| 114 | return retryTransientBootstrap(async () => { | 148 | return retryTransientBootstrap(async () => { |
| 115 | const payload: AuthBootstrapInput = { credential }; | 149 | const payload: AuthBootstrapInput = { credential }; |
| ... | @@ -123,7 +157,7 @@ async function exchangeBootstrapCredential(credential: string): Promise<AuthBoot | ... | @@ -123,7 +157,7 @@ async function exchangeBootstrapCredential(credential: string): Promise<AuthBoot |
| 123 | }); | 157 | }); |
| 124 | | 158 | |
| 125 | if (!response.ok) { | 159 | if (!response.ok) { |
| 126 | const message = await response.text(); | 160 | const message = toFriendlyBootstrapErrorMessage(response.status, await response.text()); |
| 127 | throw new BootstrapHttpError({ | 161 | throw new BootstrapHttpError({ |
| 128 | message: message || `Failed to bootstrap auth session (${response.status}).`, | 162 | message: message || `Failed to bootstrap auth session (${response.status}).`, |
| 129 | status: response.status, | 163 | status: response.status, |