All files / modules openFoodFacts.ts

80% Statements 16/20
86.36% Branches 19/22
66.66% Functions 2/3
88.23% Lines 15/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54                1x 1x   2x   1x 2x       2x 2x   2x 2x 2x   2x                   2x 1x     1x                   2x        
export interface ExternalProductInfo {
  name: string | null;
  brand?: string | null;
  quantity?: string | null;
  image?: string | null;
  source: 'openfoodfacts';
}
 
const BASE_URL = 'https://world.openfoodfacts.org/api/v2/product';
const REQUEST_TIMEOUT_MS = 5000;
 
const isOffline = () => typeof navigator !== 'undefined' && 'onLine' in navigator && !navigator.onLine;
 
export const fetchProductFromOFF = async (barcode: string): Promise<ExternalProductInfo | null> => {
  Iif (!barcode || isOffline()) {
    return null;
  }
 
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
 
  try {
    const response = await fetch(`${BASE_URL}/${barcode}.json`, { signal: controller.signal });
    Iif (!response.ok) return null;
 
    const data = (await response.json()) as {
      status?: number;
      product?: {
        product_name?: string | null;
        brands?: string | null;
        quantity?: string | null;
        image_url?: string | null;
      };
    };
 
    if (data?.status !== 1 || !data.product || !data.product.product_name) {
      return null;
    }
 
    return {
      name: data.product.product_name ?? null,
      brand: data.product.brands ?? null,
      quantity: data.product.quantity ?? null,
      image: data.product.image_url ?? null,
      source: 'openfoodfacts',
    } satisfies ExternalProductInfo;
  } catch {
    return null;
  } finally {
    clearTimeout(timeout);
  }
};