@shopify/shopify-api
Version:
Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks
128 lines (108 loc) • 3.5 kB
text/typescript
import {InvalidDeliveryMethodError} from '../error';
import {ConfigInterface} from '../base-types';
import {logger} from '../logger';
import {
AddHandlersParams,
DeliveryMethod,
WebhookHandler,
WebhookRegistry,
} from './types';
export function registry(): WebhookRegistry {
return {};
}
export function topicForStorage(topic: string): string {
return topic.toUpperCase().replace(/\/|\./g, '_');
}
export function addHandlers(
config: ConfigInterface,
webhookRegistry: WebhookRegistry,
) {
return function addHandlers(handlersToAdd: AddHandlersParams) {
for (const [topic, handlers] of Object.entries(handlersToAdd)) {
const topicKey = topicForStorage(topic);
if (Array.isArray(handlers)) {
for (const handler of handlers) {
mergeOrAddHandler(config, webhookRegistry, topicKey, handler);
}
} else {
mergeOrAddHandler(config, webhookRegistry, topicKey, handlers);
}
}
};
}
export function getTopicsAdded(webhookRegistry: WebhookRegistry) {
return function getTopicsAdded(): string[] {
return Object.keys(webhookRegistry);
};
}
export function getHandlers(webhookRegistry: WebhookRegistry) {
return function getHandlers(topic: string): WebhookHandler[] {
return webhookRegistry[topicForStorage(topic)] || [];
};
}
export function handlerIdentifier(
config: ConfigInterface,
handler: WebhookHandler,
): string {
const prefix = handler.deliveryMethod;
switch (handler.deliveryMethod) {
case DeliveryMethod.Http:
return `${prefix}_${addHostToCallbackUrl(config, handler.callbackUrl)}`;
case DeliveryMethod.EventBridge:
return `${prefix}_${handler.arn}`;
case DeliveryMethod.PubSub:
return `${prefix}_${handler.pubSubProject}:${handler.pubSubTopic}`;
default:
throw new InvalidDeliveryMethodError(
`Unrecognized delivery method '${(handler as any).deliveryMethod}'`,
);
}
}
export function addHostToCallbackUrl(
config: ConfigInterface,
callbackUrl: string,
): string {
if (callbackUrl.startsWith('/')) {
return `${config.hostScheme}://${config.hostName}${callbackUrl}`;
} else {
return callbackUrl;
}
}
function mergeOrAddHandler(
config: ConfigInterface,
webhookRegistry: WebhookRegistry,
topic: string,
handler: WebhookHandler,
) {
const log = logger(config);
handler.includeFields?.sort();
handler.metafieldNamespaces?.sort();
if (!(topic in webhookRegistry)) {
webhookRegistry[topic] = [handler];
return;
}
const identifier = handlerIdentifier(config, handler);
for (const index in webhookRegistry[topic]) {
if (!Object.prototype.hasOwnProperty.call(webhookRegistry[topic], index)) {
continue;
}
const existingHandler = webhookRegistry[topic][index];
const existingIdentifier = handlerIdentifier(config, existingHandler);
if (identifier !== existingIdentifier) {
continue;
}
if (handler.deliveryMethod === DeliveryMethod.Http) {
log.info(
`Detected multiple handlers for '${topic}', webhooks.process will call them sequentially`,
);
break;
} else {
throw new InvalidDeliveryMethodError(
`Can only add multiple handlers for a topic when deliveryMethod is Http. Please be sure that you used addHandler method once after creating ShopifyApi instance in your app. Invalid handler: ${JSON.stringify(
handler,
)}`,
);
}
}
webhookRegistry[topic].push(handler);
}