@slack/bolt
Version:
A framework for building Slack apps, fast.
216 lines • 9.83 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultUnhandledRequestHandler = exports.defaultProcessEventErrorHandler = exports.defaultAsyncDispatchErrorHandler = exports.defaultDispatchErrorHandler = exports.buildContentResponse = exports.buildSSLCheckResponse = exports.buildUrlVerificationResponse = exports.buildNoBodyResponse = exports.bufferIncomingMessage = exports.getHeader = exports.isBufferedIncomingMessage = exports.parseAndVerifyHTTPRequest = exports.parseHTTPRequestBody = exports.extractRetryReasonFromHTTPRequest = exports.extractRetryNumFromHTTPRequest = void 0;
const node_querystring_1 = require("node:querystring");
const raw_body_1 = __importDefault(require("raw-body"));
const errors_1 = require("../errors");
const verify_request_1 = require("./verify-request");
const verifyErrorPrefix = 'Failed to verify authenticity';
const extractRetryNumFromHTTPRequest = (req) => {
let retryNum;
const retryNumHeaderValue = req.headers['x-slack-retry-num'];
if (retryNumHeaderValue === undefined) {
retryNum = undefined;
}
else if (typeof retryNumHeaderValue === 'string') {
retryNum = Number.parseInt(retryNumHeaderValue, 10);
}
else if (Array.isArray(retryNumHeaderValue) && retryNumHeaderValue.length > 0) {
retryNum = Number.parseInt(retryNumHeaderValue[0], 10);
}
return retryNum;
};
exports.extractRetryNumFromHTTPRequest = extractRetryNumFromHTTPRequest;
const extractRetryReasonFromHTTPRequest = (req) => {
let retryReason;
const retryReasonHeaderValue = req.headers['x-slack-retry-reason'];
if (retryReasonHeaderValue === undefined) {
retryReason = undefined;
}
else if (typeof retryReasonHeaderValue === 'string') {
retryReason = retryReasonHeaderValue;
}
else if (Array.isArray(retryReasonHeaderValue) && retryReasonHeaderValue.length > 0) {
retryReason = retryReasonHeaderValue[0];
}
return retryReason;
};
exports.extractRetryReasonFromHTTPRequest = extractRetryReasonFromHTTPRequest;
// ------------------------------------------
// HTTP request parsing and verification
// ------------------------------------------
// biome-ignore lint/suspicious/noExplicitAny: HTTP request bodies could be anything
const parseHTTPRequestBody = (req) => {
const bodyAsString = req.rawBody.toString();
const contentType = req.headers['content-type'];
if (contentType === 'application/x-www-form-urlencoded') {
const parsedQs = (0, node_querystring_1.parse)(bodyAsString);
const { payload } = parsedQs;
if (typeof payload === 'string') {
return JSON.parse(payload);
}
return parsedQs;
}
return JSON.parse(bodyAsString);
};
exports.parseHTTPRequestBody = parseHTTPRequestBody;
const parseAndVerifyHTTPRequest = async (options, req, _res) => {
const { signingSecret } = options;
// Consume the readable stream (or use the previously consumed readable stream)
const bufferedReq = await (0, exports.bufferIncomingMessage)(req);
if (options.enabled !== undefined && !options.enabled) {
// As the validation is disabled, immediately return the buffered request
return bufferedReq;
}
const textBody = bufferedReq.rawBody.toString();
const contentType = req.headers['content-type'];
if (contentType === 'application/x-www-form-urlencoded') {
// `ssl_check=1` requests do not require x-slack-signature verification
const parsedQs = (0, node_querystring_1.parse)(textBody);
if (parsedQs?.ssl_check) {
return bufferedReq;
}
}
// Find the relevant request headers
const signature = (0, exports.getHeader)(req, 'x-slack-signature');
const requestTimestampSec = Number((0, exports.getHeader)(req, 'x-slack-request-timestamp'));
(0, verify_request_1.verifySlackRequest)({
signingSecret,
body: textBody,
headers: {
'x-slack-signature': signature,
'x-slack-request-timestamp': requestTimestampSec,
},
logger: options.logger,
});
// Checks have passed! Return the value that has a side effect (the buffered request)
return bufferedReq;
};
exports.parseAndVerifyHTTPRequest = parseAndVerifyHTTPRequest;
const isBufferedIncomingMessage = (req) => {
// TODO: why are we casting the argument if we're using this as a type guard?
return Buffer.isBuffer(req.rawBody);
};
exports.isBufferedIncomingMessage = isBufferedIncomingMessage;
const getHeader = (req, header) => {
const value = req.headers[header];
if (value === undefined || Array.isArray(value)) {
throw new Error(`${verifyErrorPrefix}: header ${header} did not have the expected type (received ${typeof value}, expected string)`);
}
return value;
};
exports.getHeader = getHeader;
const bufferIncomingMessage = async (req) => {
if ((0, exports.isBufferedIncomingMessage)(req)) {
return req;
}
const bufferedRequest = req;
bufferedRequest.rawBody = await (0, raw_body_1.default)(req);
return bufferedRequest;
};
exports.bufferIncomingMessage = bufferIncomingMessage;
// ------------------------------------------
// HTTP response builder methods
// ------------------------------------------
const buildNoBodyResponse = (res, status) => {
res.writeHead(status);
res.end();
};
exports.buildNoBodyResponse = buildNoBodyResponse;
// biome-ignore lint/suspicious/noExplicitAny: HTTP request bodies could be anything
const buildUrlVerificationResponse = (res, body) => {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ challenge: body.challenge }));
};
exports.buildUrlVerificationResponse = buildUrlVerificationResponse;
const buildSSLCheckResponse = (res) => {
res.writeHead(200);
res.end();
};
exports.buildSSLCheckResponse = buildSSLCheckResponse;
// biome-ignore lint/suspicious/noExplicitAny: HTTP request bodies could be anything
const buildContentResponse = (res, body) => {
if (!body) {
res.writeHead(200);
res.end();
}
else if (typeof body === 'string') {
res.writeHead(200);
res.end(body);
}
else {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify(body));
}
};
exports.buildContentResponse = buildContentResponse;
// ------------------------------------------
// Error handlers for event processing
// ------------------------------------------
// The default dispatchErrorHandler implementation:
// Developers can customize this behavior by passing dispatchErrorHandler to the constructor
// Note that it was not possible to make this function async due to the limitation of http module
const defaultDispatchErrorHandler = (args) => {
const { error, logger, request, response } = args;
if ('code' in error) {
if (error.code === errors_1.ErrorCode.HTTPReceiverDeferredRequestError) {
logger.info(`Unhandled HTTP request (${request.method}) made to ${request.url}`);
response.writeHead(404);
response.end();
return;
}
}
logger.error(`An unexpected error occurred during a request (${request.method}) made to ${request.url}`);
logger.debug(`Error details: ${error}`);
response.writeHead(500);
response.end();
};
exports.defaultDispatchErrorHandler = defaultDispatchErrorHandler;
const defaultAsyncDispatchErrorHandler = async (args) => {
return (0, exports.defaultDispatchErrorHandler)(args);
};
exports.defaultAsyncDispatchErrorHandler = defaultAsyncDispatchErrorHandler;
// The default processEventErrorHandler implementation:
// Developers can customize this behavior by passing processEventErrorHandler to the constructor
const defaultProcessEventErrorHandler = async (args) => {
const { error, response, logger, storedResponse } = args;
// Check if the response headers have already been sent
if (response.headersSent) {
logger.error('An unhandled error occurred after ack() called in a listener');
logger.debug(`Error details: ${error}, storedResponse: ${storedResponse}`);
return false;
}
if ('code' in error) {
if (error.code === errors_1.ErrorCode.AuthorizationError) {
// authorize function threw an exception, which means there is no valid installation data
response.writeHead(401);
response.end();
return true;
}
}
logger.error('An unhandled error occurred while Bolt processed an event');
logger.debug(`Error details: ${error}, storedResponse: ${storedResponse}`);
response.writeHead(500);
response.end();
return false;
};
exports.defaultProcessEventErrorHandler = defaultProcessEventErrorHandler;
// The default unhandledRequestHandler implementation:
// Developers can customize this behavior by passing unhandledRequestHandler to the constructor
// Note that this method cannot be an async function to align with the implementation using setTimeout
const defaultUnhandledRequestHandler = (args) => {
const { logger, response } = args;
logger.error('An incoming event was not acknowledged within 3 seconds. ' +
'Ensure that the ack() argument is called in a listener.');
// Check if the response has already been sent
if (!response.headersSent) {
// If not, set the status code and end the response to close the connection
response.writeHead(404); // Not Found
response.end();
}
};
exports.defaultUnhandledRequestHandler = defaultUnhandledRequestHandler;
//# sourceMappingURL=HTTPModuleFunctions.js.map