@kiwicom/smart-faq
Version:
Smart FAQ
162 lines (140 loc) • 3.76 kB
JavaScript
// @noflow
import {
Environment,
Network,
RecordSource,
Store,
QueryResponseCache,
} from 'relay-runtime';
import idx from 'idx';
import uuid from 'uuid/v4';
import memoizeOne from 'memoize-one';
import * as fallbacks from "../../fallbacks/"; // eslint-disable-line
import { DEFAULT_LOCALE } from '../helpers/translationUtils';
import { error } from '../cuckoo/tracker';
require('isomorphic-fetch');
// used when smart FAQ installed as dependency
const uri = 'https://graphql.kiwi.com';
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];
} else {
throw error;
}
};
const handleForbiddenError = json => {
if (
json.errors &&
json.errors.length > 0 &&
json.errors.some(
error => idx(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) {
error('GraphQL network', err, {
...errorMetadata,
errorMessage: err.message,
});
fallbackData = getFallbackData(err, operation);
}
let json = fallbackData;
if (!json) {
json = await response.json();
}
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: e.message };
}
})();
const metadata = {
...errorMetadata,
errors,
};
error('GraphQL error', null, metadata);
}
return json;
}
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(),
};
const json = 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;