UNPKG

@seratch_/bolt-fastify

Version:
252 lines 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const oauth_1 = require("@slack/oauth"); const logger_1 = require("@slack/logger"); const fastify_1 = __importDefault(require("fastify")); const bolt_1 = require("@slack/bolt"); class FastifyReceiver { constructor(options) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; this.signatureVerification = (_a = options.signatureVerification) !== null && _a !== void 0 ? _a : true; this.signingSecretProvider = options.signingSecret; this.customPropertiesExtractor = options.customPropertiesExtractor !== undefined ? options.customPropertiesExtractor : (_) => ({}); this.path = (_b = options.path) !== null && _b !== void 0 ? _b : '/slack/events'; this.unhandledRequestTimeoutMillis = (_c = options.unhandledRequestTimeoutMillis) !== null && _c !== void 0 ? _c : 3001; this.logger = (_d = options.logger) !== null && _d !== void 0 ? _d : (() => { const defaultLogger = new logger_1.ConsoleLogger(); if (options.logLevel) { defaultLogger.setLevel(options.logLevel); } return defaultLogger; })(); this.fastify = (_e = options.fastify) !== null && _e !== void 0 ? _e : (0, fastify_1.default)({ logger: true }); if (options.fastify) { this.logger.info('This Receiver replaces content type parsers in the given fastify instance. Other POST endpoints may no longer work as you expect.'); } // To do the request signature validation, bolt-js needs access to the as-is text request body const contentTypes = ['application/json', 'application/x-www-form-urlencoded']; this.fastify.removeContentTypeParser(contentTypes); this.fastify.addContentTypeParser(contentTypes, { parseAs: 'string' }, this.fastify.defaultTextParser); this.processBeforeResponse = (_f = options.processBeforeResponse) !== null && _f !== void 0 ? _f : false; this.dispatchErrorHandler = (_g = options.dispatchErrorHandler) !== null && _g !== void 0 ? _g : bolt_1.HTTPModuleFunctions.defaultAsyncDispatchErrorHandler; this.processEventErrorHandler = (_h = options.processEventErrorHandler) !== null && _h !== void 0 ? _h : bolt_1.HTTPModuleFunctions.defaultProcessEventErrorHandler; this.unhandledRequestHandler = (_j = options.unhandledRequestHandler) !== null && _j !== void 0 ? _j : bolt_1.HTTPModuleFunctions.defaultUnhandledRequestHandler; this.installerOptions = options.installerOptions; if (this.installerOptions && this.installerOptions.installPath === undefined) { this.installerOptions.installPath = '/slack/install'; } if (this.installerOptions && this.installerOptions.redirectUriPath === undefined) { this.installerOptions.redirectUriPath = '/slack/oauth_redirect'; } if (options.clientId && options.clientSecret) { this.installer = new oauth_1.InstallProvider({ ...this.installerOptions, clientId: options.clientId, clientSecret: options.clientSecret, stateSecret: options.stateSecret, installationStore: options.installationStore, logger: options.logger, logLevel: options.logLevel, installUrlOptions: { scopes: (_k = options.scopes) !== null && _k !== void 0 ? _k : [], userScopes: (_l = this.installerOptions) === null || _l === void 0 ? void 0 : _l.userScopes, metadata: (_m = this.installerOptions) === null || _m === void 0 ? void 0 : _m.metadata, redirectUri: options.redirectUri, }, }); } } async signingSecret() { if (this._sigingSecret === undefined) { this._sigingSecret = typeof this.signingSecretProvider === 'string' ? this.signingSecretProvider : await this.signingSecretProvider(); } return this._sigingSecret; } init(app) { this.app = app; if (this.installer && this.installerOptions && this.installerOptions.installPath && this.installerOptions.redirectUriPath) { this.fastify.get(this.installerOptions.installPath, async (req, res) => { var _a, _b; try { await ((_a = this.installer) === null || _a === void 0 ? void 0 : _a.handleInstallPath(req.raw, res.raw, (_b = this.installerOptions) === null || _b === void 0 ? void 0 : _b.installPathOptions)); } catch (error) { await this.dispatchErrorHandler({ error: error, logger: this.logger, request: req.raw, response: res.raw, }); } }); this.fastify.get(this.installerOptions.redirectUriPath, async (req, res) => { var _a, _b; try { await ((_a = this.installer) === null || _a === void 0 ? void 0 : _a.handleCallback(req.raw, res.raw, (_b = this.installerOptions) === null || _b === void 0 ? void 0 : _b.callbackOptions)); } catch (error) { await this.dispatchErrorHandler({ error: error, logger: this.logger, request: req.raw, response: res.raw, }); } }); } this.fastify.post(this.path, async (request, response) => { var _a; const req = request.raw; const res = response.raw; try { // eslint-disable-next-line @typescript-eslint/no-explicit-any req.rawBody = Buffer.from(request.body); // Verify authenticity let bufferedReq; try { bufferedReq = await bolt_1.HTTPModuleFunctions.parseAndVerifyHTTPRequest({ // If enabled: false, this method returns bufferredReq without verification enabled: this.signatureVerification, signingSecret: await this.signingSecret(), }, req); } catch (err) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const e = err; if (this.signatureVerification) { this.logger.warn(`Failed to parse and verify the request data: ${e.message}`); } else { this.logger.warn(`Failed to parse the request body: ${e.message}`); } bolt_1.HTTPModuleFunctions.buildNoBodyResponse(res, 401); return; } // Parse request body // eslint-disable-next-line @typescript-eslint/no-explicit-any let body; try { body = bolt_1.HTTPModuleFunctions.parseHTTPRequestBody(bufferedReq); } catch (err) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const e = err; this.logger.warn(`Malformed request body: ${e.message}`); bolt_1.HTTPModuleFunctions.buildNoBodyResponse(res, 400); return; } // Handle SSL checks if (body.ssl_check) { bolt_1.HTTPModuleFunctions.buildSSLCheckResponse(res); return; } // Handle URL verification if (body.type === 'url_verification') { bolt_1.HTTPModuleFunctions.buildUrlVerificationResponse(res, body); return; } const ack = new bolt_1.HTTPResponseAck({ logger: this.logger, processBeforeResponse: this.processBeforeResponse, unhandledRequestHandler: this.unhandledRequestHandler, unhandledRequestTimeoutMillis: this.unhandledRequestTimeoutMillis, httpRequest: bufferedReq, httpResponse: res, }); // Structure the ReceiverEvent const event = { body, ack: ack.bind(), retryNum: bolt_1.HTTPModuleFunctions.extractRetryNumFromHTTPRequest(req), retryReason: bolt_1.HTTPModuleFunctions.extractRetryReasonFromHTTPRequest(req), customProperties: this.customPropertiesExtractor(bufferedReq), }; // Send the event to the app for processing try { await ((_a = this.app) === null || _a === void 0 ? void 0 : _a.processEvent(event)); if (ack.storedResponse !== undefined) { // in the case of processBeforeResponse: true bolt_1.HTTPModuleFunctions.buildContentResponse(res, ack.storedResponse); this.logger.debug('stored response sent'); } } catch (error) { const acknowledgedByHandler = await this.processEventErrorHandler({ error: error, logger: this.logger, request: req, response: res, storedResponse: ack.storedResponse, }); if (acknowledgedByHandler) { // If the value is false, we don't touch the value as a race condition // with ack() call may occur especially when processBeforeResponse: false ack.ack(); } } } catch (error) { await this.dispatchErrorHandler({ error: error, logger: this.logger, request: req, response: res, }); } }); } start(port = 3000) { if (this.server !== undefined) { return Promise.reject(new bolt_1.ReceiverInconsistentStateError('The receiver cannot be started because it was already started.')); } return new Promise((resolve, reject) => { this.fastify.ready(); this.server = this.fastify.server; this.server.on('error', (error) => { if (this.server) { this.server.close(); } reject(error); }); this.server.on('close', () => { this.server = undefined; }); this.server.listen(port, () => { if (this.server === undefined) { return reject(new bolt_1.ReceiverInconsistentStateError('The HTTP server is unexpectedly missing')); } return resolve(this.server); }); }); } stop() { if (this.server === undefined) { const errorMessage = 'The receiver cannot be stopped because it was not started.'; return Promise.reject(new bolt_1.ReceiverInconsistentStateError(errorMessage)); } return new Promise((resolve, reject) => { var _a; (_a = this.server) === null || _a === void 0 ? void 0 : _a.close((error) => { if (error !== undefined) { return reject(error); } this.server = undefined; return resolve(); }); }); } } exports.default = FastifyReceiver; //# sourceMappingURL=FastifyReceiver.js.map