UNPKG

@kiwicom/smart-faq

Version:

181 lines (155 loc) 4.28 kB
// @noflow import 'isomorphic-fetch'; import { Environment, Network, RecordSource, Store, QueryResponseCache, } from 'relay-runtime'; import uuid from 'uuid/v4'; import memoizeOne from 'memoize-one'; import ObjectHash from 'object-hash'; import { isBrowser } from '@kiwicom/smart-faq/src/shared/helpers'; // eslint-disable-next-line import/no-useless-path-segments, import/no-unresolved import * as fallbacks from '../../fallbacks'; import { DEFAULT_LOCALE } from '../helpers/translationUtils'; const uri = 'https://graphql.kiwi.com'; export const cache = new QueryResponseCache({ size: 200, ttl: 30 * 60 * 1000 }); export const ERROR_FORBIDDEN = 'Forbidden 403'; const getFallbackData = (error, operation) => { if (operation.name in fallbacks) { return fallbacks[operation.name]; } throw error; }; const handleForbiddenError = json => { if ( json.errors && json.errors.length > 0 && json.errors.some(error => error?.extensions?.proxy?.statusCode === '403') ) { throw new Error(ERROR_FORBIDDEN); } }; const composeRequestBody = (operation, variables) => JSON.stringify({ query: operation.text, // GraphQL text from input variables, operationName: operation.name, }); const composeRequest = (headers, operation, variables) => { const body = composeRequestBody(operation, variables); return [ process.env.GRAPHQL_URI ?? uri, { method: 'POST', headers, body, }, ]; }; export async function fetchQuery(headers, operation, variables) { let fallbackData = null; let response = null; const errorMetadata = { variables, operationName: operation.name, requestId: headers['X-Request-ID'], locale: headers['Accept-Language'], }; try { response = await fetch(...composeRequest(headers, operation, variables)); } catch (err) { // eslint-disable-next-line no-console console.error('GraphQL network', { ...errorMetadata, errorMessage: err.message, }); fallbackData = getFallbackData(err, operation); } let json = fallbackData; if (!json) { const result = await response.json(); json = result; } handleForbiddenError(json); if (json.errors) { const errors = (() => { try { return JSON.stringify(json.errors); } catch (e) { // this would really suck! console.error(e); // eslint-disable-line no-console return { parseError: String(e) }; } })(); const metadata = { ...errorMetadata, errors, }; console.error('GraphQL error', metadata); // eslint-disable-line no-console } return json; } const getSSRCache = (operation, variables) => { const requestHash = ObjectHash({ query: operation.text, variables, }); if ( isBrowser() && window?.__NEXT_DATA__?.props?.requestHash === requestHash ) { return window.__NEXT_DATA__.props.rawData; } return null; }; const buildQueryFetcher = ( token: string = '', kwAuthToken: string = '', locale: string = DEFAULT_LOCALE, ) => { return async (operation, variables, cacheConfig) => { const forceFetch = cacheConfig.force; const isQuery = operation.operationKind === 'query'; if (!forceFetch && isQuery) { const cachedData = cache.get(operation.text, variables); if (cachedData) { return cachedData; } } const headers = { 'Content-Type': 'application/json', 'Accept-Language': locale, 'KW-Auth-Token': kwAuthToken, Authorization: token, 'X-Client': 'SmartFAQ', 'X-Request-ID': uuid(), }; // Use cache from next.js if available const json = getSSRCache(operation, variables) || (await fetchQuery(headers, operation, variables)); if (!forceFetch && isQuery) { cache.set(operation.text, variables, json); } return json; }; }; const getStore = memoizeOne( records => new Store(new RecordSource(records)), () => true, ); const createEnvironment = ( token: ?string, kwAuthToken: ?string, locale: ?string, records: ?Object, ) => { return new Environment({ network: Network.create(buildQueryFetcher(token, kwAuthToken, locale)), store: getStore(records), }); }; export default createEnvironment;