()
| 245 | } |
| 246 | |
| 247 | async function fetchUserSettingsOnce(): Promise<SettingsSyncFetchResult> { |
| 248 | try { |
| 249 | await checkAndRefreshOAuthTokenIfNeeded() |
| 250 | |
| 251 | const authHeaders = getSettingsSyncAuthHeaders() |
| 252 | if (authHeaders.error) { |
| 253 | return { |
| 254 | success: false, |
| 255 | error: authHeaders.error, |
| 256 | skipRetry: true, |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | const headers: Record<string, string> = { |
| 261 | ...authHeaders.headers, |
| 262 | 'User-Agent': getClaudeCodeUserAgent(), |
| 263 | } |
| 264 | |
| 265 | const endpoint = getSettingsSyncEndpoint() |
| 266 | const response = await axios.get(endpoint, { |
| 267 | headers, |
| 268 | timeout: SETTINGS_SYNC_TIMEOUT_MS, |
| 269 | validateStatus: status => status === 200 || status === 404, |
| 270 | }) |
| 271 | |
| 272 | // 404 means no settings exist yet |
| 273 | if (response.status === 404) { |
| 274 | logForDiagnosticsNoPII('info', 'settings_sync_fetch_empty') |
| 275 | return { |
| 276 | success: true, |
| 277 | isEmpty: true, |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | const parsed = UserSyncDataSchema().safeParse(response.data) |
| 282 | if (!parsed.success) { |
| 283 | logForDiagnosticsNoPII('warn', 'settings_sync_fetch_invalid_format') |
| 284 | return { |
| 285 | success: false, |
| 286 | error: 'Invalid settings sync response format', |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | logForDiagnosticsNoPII('info', 'settings_sync_fetch_success') |
| 291 | return { |
| 292 | success: true, |
| 293 | data: parsed.data, |
| 294 | isEmpty: false, |
| 295 | } |
| 296 | } catch (error) { |
| 297 | const { kind, message } = classifyAxiosError(error) |
| 298 | switch (kind) { |
| 299 | case 'auth': |
| 300 | return { |
| 301 | success: false, |
| 302 | error: 'Not authorized for settings sync', |
| 303 | skipRetry: true, |
| 304 | } |
no test coverage detected