Catch json errors a little better (#3437)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2025-07-20 15:16:27 +02:00
committed by GitHub
parent 1db0cf26ea
commit 4926e3579f
9 changed files with 54 additions and 12 deletions

View File

@@ -85,7 +85,7 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
});
if (response.ok) {
await response.json(); // Consume the response
await response.json();
setPricingStatus('success');
setLastFetchTime(new Date());
} else {

View File

@@ -1,5 +1,6 @@
import { getApiUrl, getSecretKey } from './config';
import { toast } from 'react-toastify';
import { safeJsonParse } from './utils/jsonUtils';
import builtInExtensionsData from './built-in-extensions.json';
import { toastError, toastLoading, toastSuccess } from './toasts';
@@ -181,7 +182,7 @@ export async function removeExtension(name: string, silent: boolean = false): Pr
body: JSON.stringify(sanitizeName(name)),
});
const data = await response.json();
const data = await safeJsonParse<{ error: boolean; message: string }>(response);
if (!data.error) {
if (!silent) {

View File

@@ -2,6 +2,7 @@ import { useState, useRef, useCallback, useEffect } from 'react';
import { useConfig } from '../components/ConfigContext';
import { getApiUrl, getSecretKey } from '../config';
import { useDictationSettings } from './useDictationSettings';
import { safeJsonParse } from '../utils/jsonUtils';
interface UseWhisperOptions {
onTranscription?: (text: string) => void;
@@ -151,13 +152,18 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp
} else if (response.status === 402) {
throw new Error('API quota exceeded. Please check your account limits.');
}
const errorData = await response
.json()
.catch(() => ({ error: { message: 'Transcription failed' } }));
const errorData = await safeJsonParse<{
error: { message: string };
}>(response, 'Failed to parse error response').catch(() => ({
error: { message: 'Transcription failed' },
}));
throw new Error(errorData.error?.message || 'Transcription failed');
}
const data = await response.json();
const data = await safeJsonParse<{ text: string }>(
response,
'Failed to parse transcription response'
);
if (data.text) {
onTranscription?.(data.text);
}

View File

@@ -1,6 +1,7 @@
import { Message } from '../types/message';
import { getApiUrl } from '../config';
import { FullExtensionConfig } from '../extensions';
import { safeJsonParse } from '../utils/jsonUtils';
export interface Parameter {
key: string;
@@ -70,7 +71,7 @@ export async function createRecipe(request: CreateRecipeRequest): Promise<Create
throw new Error(`Failed to create recipe: ${response.statusText} (${errorText})`);
}
return response.json();
return safeJsonParse<CreateRecipeResponse>(response, 'Server failed to create recipe:');
}
export interface EncodeRecipeRequest {

View File

@@ -1,4 +1,5 @@
import { Message } from './types/message';
import { safeJsonParse } from './utils/jsonUtils';
export interface SharedSessionDetails {
share_token: string;
@@ -35,7 +36,10 @@ export async function fetchSharedSessionDetails(
throw new Error(`Failed to fetch shared session: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const data = await safeJsonParse<SharedSessionDetails>(
response,
'Failed to parse shared session'
);
if (baseUrl != data.base_url) {
throw new Error(`Base URL mismatch for shared session: ${baseUrl} != ${data.base_url}`);
@@ -98,7 +102,10 @@ export async function createSharedSession(
throw new Error(`Failed to create shared session: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const data = await safeJsonParse<{ share_token: string }>(
response,
'Failed to parse shared session response'
);
return data.share_token;
} catch (error) {
console.error('Error creating shared session:', error);

View File

@@ -1,4 +1,5 @@
import { getApiUrl, getSecretKey } from '../config';
import { safeJsonParse } from './jsonUtils';
const getQuestionClassifierPrompt = (messageContent: string): string => `
You are a simple classifier that takes content and decides if it is asking for input
@@ -167,7 +168,7 @@ export async function ask(prompt: string): Promise<string> {
throw new Error('Failed to get response');
}
const data = await response.json();
const data = await safeJsonParse<{ response: string }>(response, 'Failed to get AI response');
return data.response;
}

View File

@@ -1,5 +1,6 @@
// Import the proper type from ConfigContext
import { getApiUrl, getSecretKey } from '../config';
import { safeJsonParse } from './jsonUtils';
export interface ModelCostInfo {
input_token_cost: number; // Cost per token for input (in USD)
@@ -47,7 +48,15 @@ async function fetchPricingForModel(
throw new Error(`API request failed with status ${response.status}`);
}
const data = await response.json();
const data = await safeJsonParse<{
pricing: Array<{
provider: string;
model: string;
input_token_cost: number;
output_token_cost: number;
currency: string;
}>;
}>(response, 'Failed to parse pricing data');
// Find the specific model pricing using the lookup provider/model
const pricing = data.pricing?.find(

View File

@@ -4,6 +4,7 @@ import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
import log from './logger';
import { safeJsonParse } from './jsonUtils';
interface GitHubRelease {
tag_name: string;
@@ -53,7 +54,10 @@ export class GitHubUpdater {
throw new Error(`GitHub API returned ${response.status}: ${response.statusText}`);
}
const release: GitHubRelease = await response.json();
const release: GitHubRelease = await safeJsonParse<GitHubRelease>(
response,
'Failed to get GitHub release information'
);
log.info(`GitHubUpdater: Found release: ${release.tag_name} (${release.name})`);
log.info(`GitHubUpdater: Release published at: ${release.published_at}`);
log.info(`GitHubUpdater: Release assets count: ${release.assets.length}`);

View File

@@ -0,0 +1,13 @@
export async function safeJsonParse<T>(
response: Response,
errorMessage: string = 'Failed to parse server response'
): Promise<T> {
try {
return (await response.json()) as T;
} catch (error) {
if (error instanceof SyntaxError) {
throw new Error(errorMessage);
}
throw error;
}
}