@seratch_/bolt-fastify
Version:
Bolt for JavaScript Extension - Fastify
246 lines • 11.8 kB
JavaScript
"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 router_1 = __importDefault(require("@koa/router"));
const koa_1 = __importDefault(require("koa"));
const http_1 = require("http");
const bolt_1 = require("@slack/bolt");
class KoaReceiver {
constructor(options) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
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.koa = (_d = options.koa) !== null && _d !== void 0 ? _d : new koa_1.default();
this.router = (_e = options.router) !== null && _e !== void 0 ? _e : new router_1.default();
this.logger = (_f = options.logger) !== null && _f !== void 0 ? _f : (() => {
const defaultLogger = new logger_1.ConsoleLogger();
if (options.logLevel) {
defaultLogger.setLevel(options.logLevel);
}
return defaultLogger;
})();
this.processBeforeResponse = (_g = options.processBeforeResponse) !== null && _g !== void 0 ? _g : false;
this.dispatchErrorHandler = (_h = options.dispatchErrorHandler) !== null && _h !== void 0 ? _h : bolt_1.HTTPModuleFunctions.defaultAsyncDispatchErrorHandler;
this.processEventErrorHandler = (_j = options.processEventErrorHandler) !== null && _j !== void 0 ? _j : bolt_1.HTTPModuleFunctions.defaultProcessEventErrorHandler;
this.unhandledRequestHandler = (_k = options.unhandledRequestHandler) !== null && _k !== void 0 ? _k : 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: (_l = options.scopes) !== null && _l !== void 0 ? _l : [],
userScopes: (_m = this.installerOptions) === null || _m === void 0 ? void 0 : _m.userScopes,
metadata: (_o = this.installerOptions) === null || _o === void 0 ? void 0 : _o.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.router.get(this.installerOptions.installPath, async (ctx) => {
var _a, _b;
try {
await ((_a = this.installer) === null || _a === void 0 ? void 0 : _a.handleInstallPath(ctx.req, ctx.res, (_b = this.installerOptions) === null || _b === void 0 ? void 0 : _b.installPathOptions));
}
catch (error) {
await this.dispatchErrorHandler({
error: error,
logger: this.logger,
request: ctx.req,
response: ctx.res,
});
}
});
this.router.get(this.installerOptions.redirectUriPath, async (ctx) => {
var _a, _b;
try {
await ((_a = this.installer) === null || _a === void 0 ? void 0 : _a.handleCallback(ctx.req, ctx.res, (_b = this.installerOptions) === null || _b === void 0 ? void 0 : _b.callbackOptions));
}
catch (error) {
await this.dispatchErrorHandler({
error: error,
logger: this.logger,
request: ctx.req,
response: ctx.res,
});
}
});
}
this.router.post(this.path, async (ctx) => {
var _a;
const { req, res } = ctx;
try {
// 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) {
// Enable routes
this.koa.use(this.router.routes()).use(this.router.allowedMethods());
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.server = (0, http_1.createServer)(this.koa.callback());
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 = KoaReceiver;
//# sourceMappingURL=KoaReceiver.js.map