UNPKG

@unito/integration-sdk

Version:

Integration SDK

163 lines (159 loc) 7.42 kB
import express from 'express'; import { InvalidHandler } from './errors.js'; import { Handler } from './handler.js'; import correlationIdMiddleware from './middlewares/correlationId.js'; import credentialsMiddleware from './middlewares/credentials.js'; import errorsMiddleware from './middlewares/errors.js'; import filtersMiddleware from './middlewares/filters.js'; import finishMiddleware from './middlewares/finish.js'; import notFoundMiddleware from './middlewares/notFound.js'; import loggerMiddleware from './middlewares/logger.js'; import startMiddleware from './middlewares/start.js'; import secretsMiddleware from './middlewares/secrets.js'; import searchMiddleware from './middlewares/search.js'; import selectsMiddleware from './middlewares/selects.js'; import relationsMiddleware from './middlewares/relations.js'; import signalMiddleware from './middlewares/signal.js'; import healthMiddleware from './middlewares/health.js'; function printErrorMessage(message) { console.error(); console.error(`\x1b[31m Oops! Something went wrong! \x1b[0m`); console.error(message); } /** * Main class for the Integration SDK providing an abstraction layer between the Integration's Graph definition * and the underlying HTTP server. * * An `Integration` instance can have multiple handlers configured to handle different routes. Upon receiving a request, * the Integration will parse the request to extract meaninful information, match the request to the appropriate handler * method and forward that information in the form a {@link Context} object. * The Integration also offer standardized error handling and logging to help you build a robust * and reliable Integration. * * See our {@link https://dev.unito.io/docs/ | documentation} for more examples on how to build an integration. */ export default class Integration { handlers; instance = undefined; port; /** * Creates a new Integration instance with default port set to 9200. * * @param options The {@link Options} to configure the Integration instance. Can be used to override the default port. */ constructor(options = {}) { this.port = options.port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 9200); this.handlers = []; } /** * Adds a group of common handlers to the integration. * * Handlers added to the integration can be one of the following: * - `ItemHandlers`: A group of handlers defining the implementation of the Operations available for a given item. * - `CredentialAccountHandlers`: A handler returning the CredentialAccount linked to the caller's credentials. * - `ParseWebhookHandlers`: A handler parsing the content of an incoming webhook. * - `WebhookSubscriptionHandlers`: A handler subscribing or unsubscribing to a particular webhook. * - `AcknowledgeWebhookHandlers`: A handler acknowledging the reception of a webhook. * * To accomodate the fact that ItemHandlers may specify multiple operations, some at the collection level, some at the * item level, we need a way to define the route for each of these operations. * To achieve this, we assume that if the last part of the path is a variable, then it is the item identifier. * * @example The following path: `/trainer/:trainerId/pokemons/:pokemonId` will lead to the following * routes: * - getCollection will be called for `GET /trainer/:trainerId/pokemons/` requests * - getItem will be called for `GET /trainer/:trainerId/pokemons/:pokemonId` requests * - createItem will be called for `POST /trainer/:trainerId/pokemons/` requests * - updateItem will be called for `PATCH /trainer/:trainerId/pokemons/:pokemonId` requests * - deleteItem will be called for `DELETE /trainer/:trainerId/pokemons/:pokemonId` requests * * @param path The path to be used as Route for the handlers. * @param handlers The Handlers definition. */ addHandler(path, handlers) { if (this.instance) { printErrorMessage(` It seems like you're trying to add a handler after the server has already started. This is probably a mistake as calling the start() function essentially starts the server and ignore any further change. To fix this error, move all your addHandler() calls before the start() function. `); process.exit(1); } try { this.handlers.push(new Handler(path, handlers)); } catch (error) { if (error instanceof InvalidHandler) { printErrorMessage(` It seems like you're trying to add an invalid handler. The exact error message is: > ${error.message} You must address this issue before trying again. `); } else { printErrorMessage(` An unexpected error happened as we were trying to add your handler. The exact error message is; > ${error.message} `); } process.exit(1); } } /** * Starts the server and listens on the specified port (default to 9200). * * @remarks * This function should be called after all the handlers have been added to the integration * and any other configuration is completed. */ start() { // Express Server initialization const app = express(); // Parse query strings with https://github.com/ljharb/qs. app.set('query parser', 'simple'); app.use(express.json()); // Must be one of the first handlers (to catch all the errors). app.use(finishMiddleware); // Instantiate internal middlewares. app.use(startMiddleware); app.use(correlationIdMiddleware); app.use(loggerMiddleware); // Making sure we log all incoming requests (except to '/health'), prior any processing. app.use((req, res, next) => { if (req.originalUrl !== '/health') { res.locals.logger.info(`Initializing request for ${req.originalUrl}`); } next(); }); // Instantiate application middlewares. These can throw, so they have an implicit dependency on the internal // middlewares such as the logger, the correlationId, and the error handling. app.get('/health', healthMiddleware); app.use(credentialsMiddleware); app.use(secretsMiddleware); app.use(filtersMiddleware); app.use(searchMiddleware); app.use(selectsMiddleware); app.use(relationsMiddleware); app.use(signalMiddleware); // Load handlers as needed. if (this.handlers.length) { for (const handler of this.handlers) { app.use(handler.generate()); } } else { printErrorMessage(` It seems like you're trying to start the server without any handler. This is probably a mistake as the server wouldn't expose any route. To fix this error, add at least one handler before calling the start() function. `); process.exit(1); } // Must be the (last - 1) handler. app.use(errorsMiddleware); // Must be the last handler. app.use(notFoundMiddleware); // Start the server. this.instance = app.listen(this.port, () => console.info(`Server started on port ${this.port}.`)); } }