UNPKG

@shopify/shopify-api

Version:

Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks

117 lines (114 loc) 5.18 kB
import { StatusCode } from '../types.mjs'; import { isOK, abstractConvertResponse } from '../../runtime/http/index.mjs'; import { InvalidWebhookError, MissingWebhookCallbackError } from '../error.mjs'; import { logger } from '../logger/index.mjs'; import { DeliveryMethod, WebhookValidationErrorReason } from './types.mjs'; import { validateFactory } from './validate.mjs'; const STATUS_TEXT_LOOKUP = { [StatusCode.Ok]: 'OK', [StatusCode.BadRequest]: 'Bad Request', [StatusCode.Unauthorized]: 'Unauthorized', [StatusCode.NotFound]: 'Not Found', [StatusCode.InternalServerError]: 'Internal Server Error', }; function process(config, webhookRegistry) { return async function process({ context, rawBody, ...adapterArgs }) { const response = { statusCode: StatusCode.Ok, statusText: STATUS_TEXT_LOOKUP[StatusCode.Ok], headers: {}, }; await logger(config).info('Receiving webhook request'); const webhookCheck = await validateFactory(config)({ rawBody, ...adapterArgs, }); let errorMessage = 'Unknown error while handling webhook'; if (webhookCheck.valid) { const handlerResult = await callWebhookHandlers(config, webhookRegistry, webhookCheck, rawBody, context); response.statusCode = handlerResult.statusCode; if (!isOK(response)) { errorMessage = handlerResult.errorMessage || errorMessage; } } else { const errorResult = await handleInvalidWebhook(config, webhookCheck); response.statusCode = errorResult.statusCode; response.statusText = STATUS_TEXT_LOOKUP[response.statusCode]; errorMessage = errorResult.errorMessage; } const returnResponse = await abstractConvertResponse(response, adapterArgs); if (!isOK(response)) { throw new InvalidWebhookError({ message: errorMessage, response: returnResponse, }); } return Promise.resolve(returnResponse); }; } async function callWebhookHandlers(config, webhookRegistry, webhookCheck, rawBody, context) { const log = logger(config); const { hmac: _hmac, valid: _valid, ...loggingContext } = webhookCheck; await log.debug('Webhook request is valid, looking for HTTP handlers to call', loggingContext); const handlers = webhookRegistry[webhookCheck.topic] || []; const response = { statusCode: StatusCode.Ok }; let found = false; for (const handler of handlers) { if (handler.deliveryMethod !== DeliveryMethod.Http) { continue; } if (!handler.callback) { response.statusCode = StatusCode.InternalServerError; response.errorMessage = "Cannot call webhooks.process with a webhook handler that doesn't have a callback"; throw new MissingWebhookCallbackError({ message: response.errorMessage, response, }); } found = true; await log.debug('Found HTTP handler, triggering it', loggingContext); try { await handler.callback(webhookCheck.topic, webhookCheck.domain, rawBody, webhookCheck.webhookId, webhookCheck.apiVersion, ...(webhookCheck?.subTopic ? webhookCheck.subTopic : ''), context); } catch (error) { response.statusCode = StatusCode.InternalServerError; response.errorMessage = error.message; } } if (!found) { await log.debug('No HTTP handlers found', loggingContext); response.statusCode = StatusCode.NotFound; response.errorMessage = `No HTTP webhooks registered for topic ${webhookCheck.topic}`; } return response; } async function handleInvalidWebhook(config, webhookCheck) { const response = { statusCode: StatusCode.InternalServerError, errorMessage: 'Unknown error while handling webhook', }; switch (webhookCheck.reason) { case WebhookValidationErrorReason.MissingHeaders: response.statusCode = StatusCode.BadRequest; response.errorMessage = `Missing one or more of the required HTTP headers to process webhooks: [${webhookCheck.missingHeaders.join(', ')}]`; break; case WebhookValidationErrorReason.MissingBody: response.statusCode = StatusCode.BadRequest; response.errorMessage = 'No body was received when processing webhook'; break; case WebhookValidationErrorReason.MissingHmac: response.statusCode = StatusCode.BadRequest; response.errorMessage = `Missing HMAC header in request`; break; case WebhookValidationErrorReason.InvalidHmac: response.statusCode = StatusCode.Unauthorized; response.errorMessage = `Could not validate request HMAC`; break; } await logger(config).debug(`Webhook request is invalid, returning ${response.statusCode}: ${response.errorMessage}`); return response; } export { process }; //# sourceMappingURL=process.mjs.map