@kiwicom/smart-faq
Version:
181 lines (155 loc) • 4.28 kB
JavaScript
// @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;