@shopify/shopify-api
Version:
Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks
187 lines (171 loc) • 5.66 kB
text/typescript
import {
HTTPResponseLog,
HTTPRetryLog,
HTTPResponseGraphQLDeprecationNotice,
LogContent,
} from '@shopify/admin-api-client';
import * as ShopifyErrors from '../error';
import {LIBRARY_NAME, StatusCode} from '../types';
import {ConfigInterface} from '../base-types';
import {SHOPIFY_API_LIBRARY_VERSION} from '../version';
import {
abstractRuntimeString,
canonicalizeHeaders,
getHeader,
} from '../../runtime';
import {logger} from '../logger';
export function getUserAgent(config: ConfigInterface): string {
let userAgentPrefix = `${LIBRARY_NAME} v${SHOPIFY_API_LIBRARY_VERSION} | ${abstractRuntimeString()}`;
if (config.userAgentPrefix) {
userAgentPrefix = `${config.userAgentPrefix} | ${userAgentPrefix}`;
}
return userAgentPrefix;
}
function serializeResponse(response: Response | any) {
if (!response) {
return {error: 'No response object provided'};
}
try {
const {status, statusText, ok, redirected, type, url, headers} = response;
const serialized: any = {
status,
statusText,
ok,
redirected,
type,
url,
};
if (headers?.entries) {
serialized.headers = Object.fromEntries(headers.entries());
} else if (headers) {
serialized.headers = headers;
}
return serialized;
} catch {
return response;
}
}
export function clientLoggerFactory(config: ConfigInterface) {
return (logContent: LogContent) => {
if (config.logger.httpRequests) {
switch (logContent.type) {
case 'HTTP-Response': {
const responseLog: HTTPResponseLog['content'] = logContent.content;
logger(config).debug('Received response for HTTP request', {
requestParams: JSON.stringify(responseLog.requestParams),
response: JSON.stringify(serializeResponse(responseLog.response)),
});
break;
}
case 'HTTP-Retry': {
const responseLog: HTTPRetryLog['content'] = logContent.content;
logger(config).debug('Retrying HTTP request', {
requestParams: JSON.stringify(responseLog.requestParams),
retryAttempt: responseLog.retryAttempt,
maxRetries: responseLog.maxRetries,
response: responseLog.lastResponse
? JSON.stringify(serializeResponse(responseLog.lastResponse))
: 'undefined',
});
break;
}
case 'HTTP-Response-GraphQL-Deprecation-Notice': {
const responseLog: HTTPResponseGraphQLDeprecationNotice['content'] =
logContent.content;
logger(config).debug(
'Received response containing Deprecated GraphQL Notice',
{
requestParams: JSON.stringify(responseLog.requestParams),
deprecationNotice: responseLog.deprecationNotice,
},
);
break;
}
default: {
logger(config).debug(`HTTP request event: ${logContent.content}`);
break;
}
}
}
};
}
export function throwFailedRequest(
body: any,
atMaxRetries: boolean,
response?: Response,
): never {
if (typeof response === 'undefined') {
const message = body?.errors?.message ?? '';
throw new ShopifyErrors.HttpRequestError(
`Http request error, no response available: ${message}`,
);
}
const responseHeaders = canonicalizeHeaders(
Object.fromEntries(response.headers.entries() ?? []),
);
if (response.status === StatusCode.Ok && body.errors.graphQLErrors) {
throw new ShopifyErrors.GraphqlQueryError({
message:
body.errors.graphQLErrors?.[0].message ?? 'GraphQL operation failed',
response: response as Record<string, any>,
headers: responseHeaders,
body: body as Record<string, any>,
});
}
const errorMessages: string[] = [];
if (body.errors) {
errorMessages.push(JSON.stringify(body.errors, null, 2));
}
const xRequestId = getHeader(responseHeaders, 'x-request-id');
if (xRequestId) {
errorMessages.push(
`If you report this error, please include this id: ${xRequestId}`,
);
}
const errorMessage = errorMessages.length
? `:\n${errorMessages.join('\n')}`
: '';
const code = response.status;
const statusText = response.statusText;
switch (true) {
case response.status === StatusCode.TooManyRequests: {
if (atMaxRetries) {
throw new ShopifyErrors.HttpMaxRetriesError(
'Attempted the maximum number of retries for HTTP request.',
);
} else {
const retryAfter = getHeader(responseHeaders, 'Retry-After');
throw new ShopifyErrors.HttpThrottlingError({
message: `Shopify is throttling requests ${errorMessage}`,
code,
statusText,
body,
headers: responseHeaders,
retryAfter: retryAfter ? parseFloat(retryAfter) : undefined,
});
}
}
case response.status >= StatusCode.InternalServerError:
if (atMaxRetries) {
throw new ShopifyErrors.HttpMaxRetriesError(
'Attempted the maximum number of retries for HTTP request.',
);
} else {
throw new ShopifyErrors.HttpInternalError({
message: `Shopify internal error${errorMessage}`,
code,
statusText,
body,
headers: responseHeaders,
});
}
default:
throw new ShopifyErrors.HttpResponseError({
message: `Received an error response (${response.status} ${response.statusText}) from Shopify${errorMessage}`,
code,
statusText,
body,
headers: responseHeaders,
});
}
}