Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@slack/interactive-messages

Package Overview
Dependencies
Maintainers
12
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@slack/interactive-messages - npm Package Compare versions

Comparing version 1.1.1 to 1.2.0

dist/adapter.d.ts

297

dist/http-handler.js

@@ -1,178 +0,135 @@

'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.errorCodes = undefined;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
exports.createHTTPHandler = createHTTPHandler;
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
var _rawBody = require('raw-body');
var _rawBody2 = _interopRequireDefault(_rawBody);
var _querystring = require('querystring');
var _querystring2 = _interopRequireDefault(_querystring);
var _crypto = require('crypto');
var _crypto2 = _interopRequireDefault(_crypto);
var _tsscmp = require('tsscmp');
var _tsscmp2 = _interopRequireDefault(_tsscmp);
var _util = require('./util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var debug = (0, _debug2.default)('@slack/interactive-messages:http-handler');
var errorCodes = exports.errorCodes = {
SIGNATURE_VERIFICATION_FAILURE: 'SLACKHTTPHANDLER_REQUEST_SIGNATURE_VERIFICATION_FAILURE',
REQUEST_TIME_FAILURE: 'SLACKHTTPHANDLER_REQUEST_TIMELIMIT_FAILURE'
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var querystring = __importStar(require("querystring"));
var debug_1 = __importDefault(require("debug"));
var raw_body_1 = __importDefault(require("raw-body"));
var crypto_1 = __importDefault(require("crypto"));
var tsscmp_1 = __importDefault(require("tsscmp"));
var errors_1 = require("./errors");
var util_1 = require("./util");
var debug = debug_1.default('@slack/interactive-messages:http-handler');
function createHTTPHandler(adapter) {
var poweredBy = (0, _util.packageIdentifier)();
/**
* Handles sending responses
*
* @param {Object} res - Response object
* @returns {Function} Returns a function used to send response
*/
function sendResponse(res) {
return function _sendResponse(dispatchResult) {
var status = dispatchResult.status,
content = dispatchResult.content;
res.statusCode = status;
res.setHeader('X-Slack-Powered-By', poweredBy);
if (typeof content === 'string') {
res.end(content);
} else if (content) {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(content));
} else {
res.end();
}
};
}
/**
* Parses raw bodies of requests
*
* @param {string} body - Raw body of request
* @returns {Object} Parsed body of the request
*/
function parseBody(body) {
var parsedBody = _querystring2.default.parse(body);
if (parsedBody.payload) {
return JSON.parse(parsedBody.payload);
var poweredBy = util_1.packageIdentifier();
/**
* Handles sending responses
*
* @param res - Response object
* @returns Returns a function used to send response
*/
function sendResponse(res) {
return function (_a) {
var status = _a.status, content = _a.content;
res.statusCode = status;
res.setHeader('X-Slack-Powered-By', poweredBy);
if (typeof content === 'string') {
res.end(content);
}
else if (!util_1.isFalsy(content)) {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(content));
}
else {
res.end();
}
};
}
return parsedBody;
}
/**
* Method to verify signature of requests
*
* @param {string} signingSecret - Signing secret used to verify request signature
* @param {Object} requestHeaders - Request headers
* @param {string} body - Raw body string
* @returns {boolean} Indicates if request is verified
*/
function verifyRequestSignature(signingSecret, requestHeaders, body) {
// Request signature
var signature = requestHeaders['x-slack-signature'];
// Request timestamp
var ts = requestHeaders['x-slack-request-timestamp'];
// Divide current date to match Slack ts format
// Subtract 5 minutes from current time
var fiveMinutesAgo = Math.floor(Date.now() / 1000) - 60 * 5;
if (ts < fiveMinutesAgo) {
debug('request is older than 5 minutes');
var error = new Error('Slack request signing verification failed');
error.code = errorCodes.REQUEST_TIME_FAILURE;
throw error;
/**
* Parses raw bodies of requests
*
* @param body - Raw body of request
* @returns Parsed body of the request
*/
function parseBody(body) {
var parsedBody = querystring.parse(body);
if (!util_1.isFalsy(parsedBody.payload)) {
return JSON.parse(parsedBody.payload);
}
return parsedBody;
}
var hmac = _crypto2.default.createHmac('sha256', signingSecret);
var _signature$split = signature.split('='),
_signature$split2 = _slicedToArray(_signature$split, 2),
version = _signature$split2[0],
hash = _signature$split2[1];
hmac.update(`${version}:${ts}:${body}`);
if (!(0, _tsscmp2.default)(hash, hmac.digest('hex'))) {
debug('request signature is not valid');
var _error = new Error('Slack request signing verification failed');
_error.code = errorCodes.SIGNATURE_VERIFICATION_FAILURE;
throw _error;
}
debug('request signing verification success');
return true;
}
/**
* Request listener used to handle Slack requests and send responses and
* verify request signatures
*
* @param {Object} req - Request object
* @param {Object} res - Response object
*/
return function slackRequestListener(req, res) {
debug('request received - method: %s, path: %s', req.method, req.url);
// Function used to send response
var respond = sendResponse(res);
// Builds body of the request from stream and returns the raw request body
(0, _rawBody2.default)(req).then(function (r) {
var rawBody = r.toString();
if (verifyRequestSignature(adapter.signingSecret, req.headers, rawBody)) {
// Request signature is verified
// Parse raw body
var body = parseBody(rawBody);
if (body.ssl_check) {
respond({ status: 200 });
return;
/**
* Method to verify signature of requests
*
* @param signingSecret - Signing secret used to verify request signature
* @param requestHeaders - The signing headers. If `req` is an incoming request, then this should be `req.headers`.
* @param body - Raw body string
* @returns Indicates if request is verified
*/
function verifyRequestSignature(signingSecret, requestHeaders, body) {
// Request signature
var signature = requestHeaders['x-slack-signature'];
// Request timestamp
var ts = parseInt(requestHeaders['x-slack-request-timestamp'], 10);
// Divide current date to match Slack ts format
// Subtract 5 minutes from current time
var fiveMinutesAgo = Math.floor(Date.now() / 1000) - (60 * 5);
if (ts < fiveMinutesAgo) {
debug('request is older than 5 minutes');
throw errors_1.errorWithCode(new Error('Slack request signing verification failed'), errors_1.ErrorCode.RequestTimeFailure);
}
var dispatchResult = adapter.dispatch(body);
if (dispatchResult) {
dispatchResult.then(respond);
} else {
// No callback was matched
debug('no callback was matched');
respond({ status: 404 });
var hmac = crypto_1.default.createHmac('sha256', signingSecret);
var _a = signature.split('='), version = _a[0], hash = _a[1];
hmac.update(version + ":" + ts + ":" + body);
if (!tsscmp_1.default(hash, hmac.digest('hex'))) {
debug('request signature is not valid');
throw errors_1.errorWithCode(new Error('Slack request signing verification failed'), errors_1.ErrorCode.SignatureVerificationFailure);
}
}
}).catch(function (error) {
if (error.code === errorCodes.SIGNATURE_VERIFICATION_FAILURE || error.code === errorCodes.REQUEST_TIME_FAILURE) {
respond({ status: 404 });
} else if (process.env.NODE_ENV === 'development') {
respond({ status: 500, content: error.message });
} else {
respond({ status: 500 });
}
});
};
debug('request signing verification success');
return true;
}
/**
* Request listener used to handle Slack requests and send responses and
* verify request signatures
*/
return function (req, res) {
debug('request received - method: %s, path: %s', req.method, req.url);
// Function used to send response
var respond = sendResponse(res);
// Builds body of the request from stream and returns the raw request body
raw_body_1.default(req)
.then(function (bodyBuf) {
var rawBody = bodyBuf.toString();
if (verifyRequestSignature(adapter.signingSecret, req.headers, rawBody)) {
// Request signature is verified
// Parse raw body
var body = parseBody(rawBody);
if (body.ssl_check) {
respond({ status: 200 });
return;
}
var dispatchResult = adapter.dispatch(body);
if (dispatchResult !== undefined) {
// TODO: handle this after responding?
// tslint:disable-next-line no-floating-promises
dispatchResult.then(respond);
}
else {
// No callback was matched
debug('no callback was matched');
respond({ status: 404 });
}
}
}).catch(function (error) {
if (error.code === errors_1.ErrorCode.SignatureVerificationFailure || error.code === errors_1.ErrorCode.RequestTimeFailure) {
respond({ status: 404 });
}
else if (process.env.NODE_ENV === 'development') {
respond({ status: 500, content: error.message });
}
else {
respond({ status: 500 });
}
});
};
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9odHRwLWhhbmRsZXIuanMiXSwibmFtZXMiOlsiY3JlYXRlSFRUUEhhbmRsZXIiLCJkZWJ1ZyIsImVycm9yQ29kZXMiLCJTSUdOQVRVUkVfVkVSSUZJQ0FUSU9OX0ZBSUxVUkUiLCJSRVFVRVNUX1RJTUVfRkFJTFVSRSIsImFkYXB0ZXIiLCJwb3dlcmVkQnkiLCJzZW5kUmVzcG9uc2UiLCJyZXMiLCJfc2VuZFJlc3BvbnNlIiwiZGlzcGF0Y2hSZXN1bHQiLCJzdGF0dXMiLCJjb250ZW50Iiwic3RhdHVzQ29kZSIsInNldEhlYWRlciIsImVuZCIsIkpTT04iLCJzdHJpbmdpZnkiLCJwYXJzZUJvZHkiLCJib2R5IiwicGFyc2VkQm9keSIsInF1ZXJ5c3RyaW5nIiwicGFyc2UiLCJwYXlsb2FkIiwidmVyaWZ5UmVxdWVzdFNpZ25hdHVyZSIsInNpZ25pbmdTZWNyZXQiLCJyZXF1ZXN0SGVhZGVycyIsInNpZ25hdHVyZSIsInRzIiwiZml2ZU1pbnV0ZXNBZ28iLCJNYXRoIiwiZmxvb3IiLCJEYXRlIiwibm93IiwiZXJyb3IiLCJFcnJvciIsImNvZGUiLCJobWFjIiwiY3J5cHRvIiwiY3JlYXRlSG1hYyIsInNwbGl0IiwidmVyc2lvbiIsImhhc2giLCJ1cGRhdGUiLCJkaWdlc3QiLCJzbGFja1JlcXVlc3RMaXN0ZW5lciIsInJlcSIsIm1ldGhvZCIsInVybCIsInJlc3BvbmQiLCJ0aGVuIiwiciIsInJhd0JvZHkiLCJ0b1N0cmluZyIsImhlYWRlcnMiLCJzc2xfY2hlY2siLCJkaXNwYXRjaCIsImNhdGNoIiwicHJvY2VzcyIsImVudiIsIk5PREVfRU5WIiwibWVzc2FnZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O1FBY2dCQSxpQixHQUFBQSxpQjs7QUFkaEI7Ozs7QUFDQTs7OztBQUNBOzs7O0FBQ0E7Ozs7QUFDQTs7OztBQUNBOzs7O0FBRUEsSUFBTUMsUUFBUSxxQkFBYSwwQ0FBYixDQUFkOztBQUVPLElBQU1DLGtDQUFhO0FBQ3hCQyxrQ0FBZ0Ms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YnVnRmFjdG9yeSBmcm9tICdkZWJ1Zyc7XG5pbXBvcnQgZ2V0UmF3Qm9keSBmcm9tICdyYXctYm9keSc7XG5pbXBvcnQgcXVlcnlzdHJpbmcgZnJvbSAncXVlcnlzdHJpbmcnO1xuaW1wb3J0IGNyeXB0byBmcm9tICdjcnlwdG8nO1xuaW1wb3J0IHRpbWluZ1NhZmVDb21wYXJlIGZyb20gJ3Rzc2NtcCc7XG5pbXBvcnQgeyBwYWNrYWdlSWRlbnRpZmllciB9IGZyb20gJy4vdXRpbCc7XG5cbmNvbnN0IGRlYnVnID0gZGVidWdGYWN0b3J5KCdAc2xhY2svaW50ZXJhY3RpdmUtbWVzc2FnZXM6aHR0cC1oYW5kbGVyJyk7XG5cbmV4cG9ydCBjb25zdCBlcnJvckNvZGVzID0ge1xuICBTSUdOQVRVUkVfVkVSSUZJQ0FUSU9OX0ZBSUxVUkU6ICdTTEFDS0hUVFBIQU5ETEVSX1JFUVVFU1RfU0lHTkFUVVJFX1ZFUklGSUNBVElPTl9GQUlMVVJFJyxcbiAgUkVRVUVTVF9USU1FX0ZBSUxVUkU6ICdTTEFDS0hUVFBIQU5ETEVSX1JFUVVFU1RfVElNRUxJTUlUX0ZBSUxVUkUnLFxufTtcblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUhUVFBIYW5kbGVyKGFkYXB0ZXIpIHtcbiAgY29uc3QgcG93ZXJlZEJ5ID0gcGFja2FnZUlkZW50aWZpZXIoKTtcblxuICAvKipcbiAgICogSGFuZGxlcyBzZW5kaW5nIHJlc3BvbnNlc1xuICAgKlxuICAgKiBAcGFyYW0ge09iamVjdH0gcmVzIC0gUmVzcG9uc2Ugb2JqZWN0XG4gICAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyBhIGZ1bmN0aW9uIHVzZWQgdG8gc2VuZCByZXNwb25zZVxuICAgKi9cbiAgZnVuY3Rpb24gc2VuZFJlc3BvbnNlKHJlcykge1xuICAgIHJldHVybiBmdW5jdGlvbiBfc2VuZFJlc3BvbnNlKGRpc3BhdGNoUmVzdWx0KSB7XG4gICAgICBjb25zdCB7IHN0YXR1cywgY29udGVudCB9ID0gZGlzcGF0Y2hSZXN1bHQ7XG4gICAgICByZXMuc3RhdHVzQ29kZSA9IHN0YXR1cztcbiAgICAgIHJlcy5zZXRIZWFkZXIoJ1gtU2xhY2stUG93ZXJlZC1CeScsIHBvd2VyZWRCeSk7XG4gICAgICBpZiAodHlwZW9mIGNvbnRlbnQgPT09ICdzdHJpbmcnKSB7XG4gICAgICAgIHJlcy5lbmQoY29udGVudCk7XG4gICAgICB9IGVsc2UgaWYgKGNvbnRlbnQpIHtcbiAgICAgICAgcmVzLnNldEhlYWRlcignQ29udGVudC1UeXBlJywgJ2FwcGxpY2F0aW9uL2pzb24nKTtcbiAgICAgICAgcmVzLmVuZChKU09OLnN0cmluZ2lmeShjb250ZW50KSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXMuZW5kKCk7XG4gICAgICB9XG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBQYXJzZXMgcmF3IGJvZGllcyBvZiByZXF1ZXN0c1xuICAgKlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYm9keSAtIFJhdyBib2R5IG9mIHJlcXVlc3RcbiAgICogQHJldHVybnMge09iamVjdH0gUGFyc2VkIGJvZHkgb2YgdGhlIHJlcXVlc3RcbiAgICovXG4gIGZ1bmN0aW9uIHBhcnNlQm9keShib2R5KSB7XG4gICAgY29uc3QgcGFyc2VkQm9keSA9IHF1ZXJ5c3RyaW5nLnBhcnNlKGJvZHkpO1xuICAgIGlmIChwYXJzZWRCb2R5LnBheWxvYWQpIHtcbiAgICAgIHJldHVybiBKU09OLnBhcnNlKHBhcnNlZEJvZHkucGF5bG9hZCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHBhcnNlZEJvZHk7XG4gIH1cblxuICAvKipcbiAgICogTWV0aG9kIHRvIHZlcmlmeSBzaWduYXR1cmUgb2YgcmVxdWVzdHNcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IHNpZ25pbmdTZWNyZXQgLSBTaWduaW5nIHNlY3JldCB1c2VkIHRvIHZlcmlmeSByZXF1ZXN0IHNpZ25hdHVyZVxuICAgKiBAcGFyYW0ge09iamVjdH0gcmVxdWVzdEhlYWRlcnMgLSBSZXF1ZXN0IGhlYWRlcnNcbiAgICogQHBhcmFtIHtzdHJpbmd9IGJvZHkgLSBSYXcgYm9keSBzdHJpbmdcbiAgICogQHJldHVybnMge2Jvb2xlYW59IEluZGljYXRlcyBpZiByZXF1ZXN0IGlzIHZlcmlmaWVkXG4gICAqL1xuICBmdW5jdGlvbiB2ZXJpZnlSZXF1ZXN0U2lnbmF0dXJlKHNpZ25pbmdTZWNyZXQsIHJlcXVlc3RIZWFkZXJzLCBib2R5KSB7XG4gICAgLy8gUmVxdWVzdCBzaWduYXR1cmVcbiAgICBjb25zdCBzaWduYXR1cmUgPSByZXF1ZXN0SGVhZGVyc1sneC1zbGFjay1zaWduYXR1cmUnXTtcbiAgICAvLyBSZXF1ZXN0IHRpbWVzdGFtcFxuICAgIGNvbnN0IHRzID0gcmVxdWVzdEhlYWRlcnNbJ3gtc2xhY2stcmVxdWVzdC10aW1lc3RhbXAnXTtcblxuICAgIC8vIERpdmlkZSBjdXJyZW50IGRhdGUgdG8gbWF0Y2ggU2xhY2sgdHMgZm9ybWF0XG4gICAgLy8gU3VidHJhY3QgNSBtaW51dGVzIGZyb20gY3VycmVudCB0aW1lXG4gICAgY29uc3QgZml2ZU1pbnV0ZXNBZ28gPSBNYXRoLmZsb29yKERhdGUubm93KCkgLyAxMDAwKSAtICg2MCAqIDUpO1xuXG4gICAgaWYgKHRzIDwgZml2ZU1pbnV0ZXNBZ28pIHtcbiAgICAgIGRlYnVnKCdyZXF1ZXN0IGlzIG9sZGVyIHRoYW4gNSBtaW51dGVzJyk7XG4gICAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcignU2xhY2sgcmVxdWVzdCBzaWduaW5nIHZlcmlmaWNhdGlvbiBmYWlsZWQnKTtcbiAgICAgIGVycm9yLmNvZGUgPSBlcnJvckNvZGVzLlJFUVVFU1RfVElNRV9GQUlMVVJFO1xuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuXG4gICAgY29uc3QgaG1hYyA9IGNyeXB0by5jcmVhdGVIbWFjKCdzaGEyNTYnLCBzaWduaW5nU2VjcmV0KTtcbiAgICBjb25zdCBbdmVyc2lvbiwgaGFzaF0gPSBzaWduYXR1cmUuc3BsaXQoJz0nKTtcbiAgICBobWFjLnVwZGF0ZShgJHt2ZXJzaW9ufToke3RzfToke2JvZHl9YCk7XG5cbiAgICBpZiAoIXRpbWluZ1NhZmVDb21wYXJlKGhhc2gsIGhtYWMuZGlnZXN0KCdoZXgnKSkpIHtcbiAgICAgIGRlYnVnKCdyZXF1ZXN0IHNpZ25hdHVyZSBpcyBub3QgdmFsaWQnKTtcbiAgICAgIGNvbnN0IGVycm9yID0gbmV3IEVycm9yKCdTbGFjayByZXF1ZXN0IHNpZ25pbmcgdmVyaWZpY2F0aW9uIGZhaWxlZCcpO1xuICAgICAgZXJyb3IuY29kZSA9IGVycm9yQ29kZXMuU0lHTkFUVVJFX1ZFUklGSUNBVElPTl9GQUlMVVJFO1xuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuXG4gICAgZGVidWcoJ3JlcXVlc3Qgc2lnbmluZyB2ZXJpZmljYXRpb24gc3VjY2VzcycpO1xuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlcXVlc3QgbGlzdGVuZXIgdXNlZCB0byBoYW5kbGUgU2xhY2sgcmVxdWVzdHMgYW5kIHNlbmQgcmVzcG9uc2VzIGFuZFxuICAgKiB2ZXJpZnkgcmVxdWVzdCBzaWduYXR1cmVzXG4gICAqXG4gICAqIEBwYXJhbSB7T2JqZWN0fSByZXEgLSBSZXF1ZXN0IG9iamVjdFxuICAgKiBAcGFyYW0ge09iamVjdH0gcmVzIC0gUmVzcG9uc2Ugb2JqZWN0XG4gICAqL1xuICByZXR1cm4gZnVuY3Rpb24gc2xhY2tSZXF1ZXN0TGlzdGVuZXIocmVxLCByZXMpIHtcbiAgICBkZWJ1ZygncmVxdWVzdCByZWNlaXZlZCAtIG1ldGhvZDogJXMsIHBhdGg6ICVzJywgcmVxLm1ldGhvZCwgcmVxLnVybCk7XG4gICAgLy8gRnVuY3Rpb24gdXNlZCB0byBzZW5kIHJlc3BvbnNlXG4gICAgY29uc3QgcmVzcG9uZCA9IHNlbmRSZXNwb25zZShyZXMpO1xuXG4gICAgLy8gQnVpbGRzIGJvZHkgb2YgdGhlIHJlcXVlc3QgZnJvbSBzdHJlYW0gYW5kIHJldHVybnMgdGhlIHJhdyByZXF1ZXN0IGJvZHlcbiAgICBnZXRSYXdCb2R5KHJlcSlcbiAgICAgIC50aGVuKChyKSA9PiB7XG4gICAgICAgIGNvbnN0IHJhd0JvZHkgPSByLnRvU3RyaW5nKCk7XG5cbiAgICAgICAgaWYgKHZlcmlmeVJlcXVlc3RTaWduYXR1cmUoYWRhcHRlci5zaWduaW5nU2VjcmV0LCByZXEuaGVhZGVycywgcmF3Qm9keSkpIHtcbiAgICAgICAgICAvLyBSZXF1ZXN0IHNpZ25hdHVyZSBpcyB2ZXJpZmllZFxuICAgICAgICAgIC8vIFBhcnNlIHJhdyBib2R5XG4gICAgICAgICAgY29uc3QgYm9keSA9IHBhcnNlQm9keShyYXdCb2R5KTtcblxuICAgICAgICAgIGlmIChib2R5LnNzbF9jaGVjaykge1xuICAgICAgICAgICAgcmVzcG9uZCh7IHN0YXR1czogMjAwIH0pO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGNvbnN0IGRpc3BhdGNoUmVzdWx0ID0gYWRhcHRlci5kaXNwYXRjaChib2R5KTtcblxuICAgICAgICAgIGlmIChkaXNwYXRjaFJlc3VsdCkge1xuICAgICAgICAgICAgZGlzcGF0Y2hSZXN1bHQudGhlbihyZXNwb25kKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gTm8gY2FsbGJhY2sgd2FzIG1hdGNoZWRcbiAgICAgICAgICAgIGRlYnVnKCdubyBjYWxsYmFjayB3YXMgbWF0Y2hlZCcpO1xuICAgICAgICAgICAgcmVzcG9uZCh7IHN0YXR1czogNDA0IH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSkuY2F0Y2goKGVycm9yKSA9PiB7XG4gICAgICAgIGlmIChlcnJvci5jb2RlID09PSBlcnJvckNvZGVzLlNJR05BVFVSRV9WRVJJRklDQVRJT05fRkFJTFVSRSB8fFxuICAgICAgICAgIGVycm9yLmNvZGUgPT09IGVycm9yQ29kZXMuUkVRVUVTVF9USU1FX0ZBSUxVUkUpIHtcbiAgICAgICAgICByZXNwb25kKHsgc3RhdHVzOiA0MDQgfSk7XG4gICAgICAgIH0gZWxzZSBpZiAocHJvY2Vzcy5lbnYuTk9ERV9FTlYgPT09ICdkZXZlbG9wbWVudCcpIHtcbiAgICAgICAgICByZXNwb25kKHsgc3RhdHVzOiA1MDAsIGNvbnRlbnQ6IGVycm9yLm1lc3NhZ2UgfSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmVzcG9uZCh7IHN0YXR1czogNTAwIH0pO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgfTtcbn1cbiJdfQ==
exports.createHTTPHandler = createHTTPHandler;
//# sourceMappingURL=http-handler.js.map

@@ -1,32 +0,19 @@

'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.errorCodes = undefined;
exports.createMessageAdapter = createMessageAdapter;
var _adapter = require('./adapter');
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var adapter_1 = require("./adapter");
var errors_1 = require("./errors");
/**
* Dictionary of error codes that may appear on errors emitted from this package's objects
* @readonly
* @enum {string}
*/
var errorCodes = exports.errorCodes = _adapter.errorCodes;
exports.errorCodes = {
BODY_PARSER_NOT_PERMITTED: errors_1.ErrorCode.BodyParserNotPermitted,
};
// TODO: export other error codes
/**
* Factory method to create an instance of {@link module:adapter~SlackMessageAdapter}
*
* @param {string} signingSecret
* @param {Object} options
* @returns {module:adapter~SlackMessageAdapter}
* Factory method to create an instance of {@link SlackMessageAdapter}
*/
/**
* @module @slack/interactive-messages
*/
function createMessageAdapter(signingSecret, options) {
return new _adapter.SlackMessageAdapter(signingSecret, options);
return new adapter_1.SlackMessageAdapter(signingSecret, options);
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC5qcyJdLCJuYW1lcyI6WyJjcmVhdGVNZXNzYWdlQWRhcHRlciIsImVycm9yQ29kZXMiLCJhZGFwdGVyRXJyb3JDb2RlcyIsInNpZ25pbmdTZWNyZXQiLCJvcHRpb25zIiwiU2xhY2tNZXNzYWdlQWRhcHRlciJdLCJtYXBwaW5ncyI6Ijs7Ozs7O1FBbUJnQkEsb0IsR0FBQUEsb0I7O0FBaEJoQjs7QUFFQTs7Ozs7QUFLTyxJQUFNQyxrQ0FBYUMsbUJBQW5COztBQUVQOzs7Ozs7O0FBWkE7OztBQW1CTyxTQUFTRixvQkFBVCxDQUE4QkcsYUFBOUIsRUFBNkNDLE9BQTdDLEVBQXNEO0FBQzNELFNBQU8sSUFBSUMsNEJBQUosQ0FBd0JGLGFBQXhCLEVBQXVDQyxPQUF2QyxDQUFQO0FBQ0QiLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBtb2R1bGUgQHNsYWNrL2ludGVyYWN0aXZlLW1lc3NhZ2VzXG4gKi9cbmltcG9ydCB7IFNsYWNrTWVzc2FnZUFkYXB0ZXIsIGVycm9yQ29kZXMgYXMgYWRhcHRlckVycm9yQ29kZXMgfSBmcm9tICcuL2FkYXB0ZXInO1xuXG4vKipcbiAqIERpY3Rpb25hcnkgb2YgZXJyb3IgY29kZXMgdGhhdCBtYXkgYXBwZWFyIG9uIGVycm9ycyBlbWl0dGVkIGZyb20gdGhpcyBwYWNrYWdlJ3Mgb2JqZWN0c1xuICogQHJlYWRvbmx5XG4gKiBAZW51bSB7c3RyaW5nfVxuICovXG5leHBvcnQgY29uc3QgZXJyb3JDb2RlcyA9IGFkYXB0ZXJFcnJvckNvZGVzO1xuXG4vKipcbiAqIEZhY3RvcnkgbWV0aG9kIHRvIGNyZWF0ZSBhbiBpbnN0YW5jZSBvZiB7QGxpbmsgbW9kdWxlOmFkYXB0ZXJ+U2xhY2tNZXNzYWdlQWRhcHRlcn1cbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gc2lnbmluZ1NlY3JldFxuICogQHBhcmFtIHtPYmplY3R9IG9wdGlvbnNcbiAqIEByZXR1cm5zIHttb2R1bGU6YWRhcHRlcn5TbGFja01lc3NhZ2VBZGFwdGVyfVxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlTWVzc2FnZUFkYXB0ZXIoc2lnbmluZ1NlY3JldCwgb3B0aW9ucykge1xuICByZXR1cm4gbmV3IFNsYWNrTWVzc2FnZUFkYXB0ZXIoc2lnbmluZ1NlY3JldCwgb3B0aW9ucyk7XG59XG4iXX0=
exports.createMessageAdapter = createMessageAdapter;
//# sourceMappingURL=index.js.map

@@ -1,42 +0,34 @@

'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.errorCodes = undefined;
exports.promiseTimeout = promiseTimeout;
exports.packageIdentifier = packageIdentifier;
var _os = require('os');
var _os2 = _interopRequireDefault(_os);
var _package = require('../package.json');
var _package2 = _interopRequireDefault(_package);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function escape(s) {
return s.replace('/', ':').replace(' ', '_');
}
var errorCodes = exports.errorCodes = {
PROMISE_TIMEOUT: 'SLACKMESSAGEUTIL_PROMISE_TIMEOUT'
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var os_1 = __importDefault(require("os"));
var errors_1 = require("./errors");
var pkg = require('../package.json'); // tslint:disable-line
function escape(s) { return s.replace('/', ':').replace(' ', '_'); }
exports.errorCodes = {
PROMISE_TIMEOUT: errors_1.ErrorCode.PromiseTimeout,
};
/**
* Creates a timeout on a promise.
*
* @param ms - The timeout duration.
* @param promise - The promise that will timeout.
*/
function promiseTimeout(ms, promise) {
// Create a promise that rejects in <ms> milliseconds
var timeout = new Promise(function (resolve, reject) {
var id = setTimeout(function () {
clearTimeout(id);
var error = new Error('Promise timed out');
error.code = errorCodes.PROMISE_TIMEOUT;
reject(error);
}, ms);
});
// Returns a race between our timeout and the passed in promise
return Promise.race([promise, timeout]);
// Create a promise that rejects in `ms` milliseconds
var timeout = new Promise(function (_resolve, reject) {
var id = setTimeout(function () {
clearTimeout(id);
reject(errors_1.errorWithCode(new Error('Promise timed out'), errors_1.ErrorCode.PromiseTimeout));
}, ms);
});
// Race between our timeout and the passed in `promise`
return Promise.race([
promise,
timeout,
]);
}
exports.promiseTimeout = promiseTimeout;
// NOTE: before this can be an external module:

@@ -48,16 +40,23 @@ // 1. are all the JS features supported back to a reasonable version?

// there will potentially be more named exports in this file
// eslint-disable-next-line import/prefer-default-export
function packageIdentifier() {
var addons = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var identifierMap = Object.assign({
[`${_package2.default.name}`]: _package2.default.version,
[`${_os2.default.platform()}`]: _os2.default.release(),
node: process.version.replace('v', '')
}, addons);
return Object.keys(identifierMap).reduce(function (acc, k) {
return `${acc} ${escape(k)}/${escape(identifierMap[k])}`;
}, '');
function packageIdentifier(addons) {
var _a;
if (addons === void 0) { addons = {}; }
var identifierMap = Object.assign((_a = {},
_a[pkg.name] = pkg.version,
_a[os_1.default.platform()] = os_1.default.release(),
_a.node = process.version.replace('v', ''),
_a), addons);
return Object.keys(identifierMap).reduce(function (acc, k) { return acc + " " + escape(k) + "/" + escape(identifierMap[k]); }, '');
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLmpzIl0sIm5hbWVzIjpbInByb21pc2VUaW1lb3V0IiwicGFja2FnZUlkZW50aWZpZXIiLCJlc2NhcGUiLCJzIiwicmVwbGFjZSIsImVycm9yQ29kZXMiLCJQUk9NSVNFX1RJTUVPVVQiLCJtcyIsInByb21pc2UiLCJ0aW1lb3V0IiwiUHJvbWlzZSIsInJlc29sdmUiLCJyZWplY3QiLCJpZCIsInNldFRpbWVvdXQiLCJjbGVhclRpbWVvdXQiLCJlcnJvciIsIkVycm9yIiwiY29kZSIsInJhY2UiLCJhZGRvbnMiLCJpZGVudGlmaWVyTWFwIiwiT2JqZWN0IiwiYXNzaWduIiwicGtnIiwibmFtZSIsInZlcnNpb24iLCJvcyIsInBsYXRmb3JtIiwicmVsZWFzZSIsIm5vZGUiLCJwcm9jZXNzIiwia2V5cyIsInJlZHVjZSIsImFjYyIsImsiXSwibWFwcGluZ3MiOiI7Ozs7OztRQVNnQkEsYyxHQUFBQSxjO1FBd0JBQyxpQixHQUFBQSxpQjs7QUFqQ2hCOzs7O0FBQ0E7Ozs7OztBQUVBLFNBQVNDLE1BQVQsQ0FBZ0JDLENBQWhCLEVBQW1CO0FBQUUsU0FBT0EsRUFBRUMsT0FBRixDQUFVLEdBQVYsRUFBZSxHQUFmLEVBQW9CQSxPQUFwQixDQUE0QixHQUE1QixFQUFpQyxHQUFqQyxDQUFQO0FBQStDOztBQUU3RCxJQUFNQyxrQ0FBYTtBQUN4QkMsbUJBQWlCO0FBRE8sQ0FBbkI7O0FBSUEsU0FBU04sY0FBVCxDQUF3Qk8sRUFBeEIsRUFBNEJDLE9BQTVCLEVBQXFDO0FBQzFDO0FBQ0EsTUFBTUMsVUFBVSxJQUFJQyxPQUFKLENBQVksVUFBQ0MsT0FBRCxFQUFVQyxNQUFWLEVBQXFCO0FBQy9DLFFBQU1DLEtBQUtDLFdBQVcsWUFBTTtBQUMxQkMsbUJBQWFGLEVBQWI7QUFDQSxVQUFNRyxRQUFRLElBQUlDLEtBQUosQ0FBVSxtQkFBVixDQUFkO0FBQ0FELFlBQU1FLElBQU4sR0FBYWIsV0FBV0MsZUFBeEI7QUFDQU0sYUFBT0ksS0FBUDtBQUNELEtBTFUsRUFLUlQsRUFMUSxDQUFYO0FBTUQsR0FQZSxDQUFoQjtBQVFBO0FBQ0EsU0FBT0csUUFBUVMsSUFBUixDQUFhLENBQ2xCWCxPQURrQixFQUVsQkMsT0FGa0IsQ0FBYixDQUFQO0FBSUQ7O0FBRUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDTyxTQUFTUixpQkFBVCxHQUF3QztBQUFBLE1BQWJtQixNQUFhLHVFQUFKLEVBQUk7O0FBQzdDLE1BQU1DLGdCQUFnQkMsT0FBT0MsTUFBUCxDQUFjO0FBQ2xDLEtBQUUsR0FBRUMsa0JBQUlDLElBQUssRUFBYixHQUFpQkQsa0JBQUlFLE9BRGE7QUFFbEMsS0FBRSxHQUFFQyxhQUFHQyxRQUFILEVBQWMsRUFBbEIsR0FBc0JELGFBQUdFLE9BQUgsRUFGWTtBQUdsQ0MsVUFBTUMsUUFBUUwsT0FBUixDQUFnQnRCLE9BQWhCLENBQXdCLEdBQXhCLEVBQTZCLEVBQTdCO0FBSDRCLEdBQWQsRUFJbkJnQixNQUptQixDQUF0QjtBQUtBLFNBQU9FLE9BQU9VLElBQVAsQ0FBWVgsYUFBWixFQUEyQlksTUFBM0IsQ0FBa0MsVUFBQ0MsR0FBRCxFQUFNQyxDQUFOO0FBQUEsV0FBYSxHQUFFRCxHQUFJLElBQUdoQyxPQUFPaUMsQ0FBUCxDQUFVLElBQUdqQyxPQUFPbUIsY0FBY2MsQ0FBZCxDQUFQLENBQXlCLEVBQTVEO0FBQUEsR0FBbEMsRUFBaUcsRUFBakcsQ0FBUDtBQUNEIiwiZmlsZSI6InV0aWwuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgb3MgZnJvbSAnb3MnO1xuaW1wb3J0IHBrZyBmcm9tICcuLi9wYWNrYWdlLmpzb24nO1xuXG5mdW5jdGlvbiBlc2NhcGUocykgeyByZXR1cm4gcy5yZXBsYWNlKCcvJywgJzonKS5yZXBsYWNlKCcgJywgJ18nKTsgfVxuXG5leHBvcnQgY29uc3QgZXJyb3JDb2RlcyA9IHtcbiAgUFJPTUlTRV9USU1FT1VUOiAnU0xBQ0tNRVNTQUdFVVRJTF9QUk9NSVNFX1RJTUVPVVQnLFxufTtcblxuZXhwb3J0IGZ1bmN0aW9uIHByb21pc2VUaW1lb3V0KG1zLCBwcm9taXNlKSB7XG4gIC8vIENyZWF0ZSBhIHByb21pc2UgdGhhdCByZWplY3RzIGluIDxtcz4gbWlsbGlzZWNvbmRzXG4gIGNvbnN0IHRpbWVvdXQgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgY29uc3QgaWQgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIGNsZWFyVGltZW91dChpZCk7XG4gICAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcignUHJvbWlzZSB0aW1lZCBvdXQnKTtcbiAgICAgIGVycm9yLmNvZGUgPSBlcnJvckNvZGVzLlBST01JU0VfVElNRU9VVDtcbiAgICAgIHJlamVjdChlcnJvcik7XG4gICAgfSwgbXMpO1xuICB9KTtcbiAgLy8gUmV0dXJucyBhIHJhY2UgYmV0d2VlbiBvdXIgdGltZW91dCBhbmQgdGhlIHBhc3NlZCBpbiBwcm9taXNlXG4gIHJldHVybiBQcm9taXNlLnJhY2UoW1xuICAgIHByb21pc2UsXG4gICAgdGltZW91dCxcbiAgXSk7XG59XG5cbi8vIE5PVEU6IGJlZm9yZSB0aGlzIGNhbiBiZSBhbiBleHRlcm5hbCBtb2R1bGU6XG4vLyAxLiBhcmUgYWxsIHRoZSBKUyBmZWF0dXJlcyBzdXBwb3J0ZWQgYmFjayB0byBhIHJlYXNvbmFibGUgdmVyc2lvbj9cbi8vICAgIGRlZmF1bHQgcGFyYW1zLCB0ZW1wbGF0ZSBzdHJpbmdzLCBjb21wdXRlZCBwcm9wZXJ0eSBuYW1lc1xuLy8gMi4gYWNjZXNzIHRvIGBwa2dgIHdpbGwgY2hhbmdlXG4vLyAzLiB0ZXN0c1xuLy8gdGhlcmUgd2lsbCBwb3RlbnRpYWxseSBiZSBtb3JlIG5hbWVkIGV4cG9ydHMgaW4gdGhpcyBmaWxlXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L3ByZWZlci1kZWZhdWx0LWV4cG9ydFxuZXhwb3J0IGZ1bmN0aW9uIHBhY2thZ2VJZGVudGlmaWVyKGFkZG9ucyA9IHt9KSB7XG4gIGNvbnN0IGlkZW50aWZpZXJNYXAgPSBPYmplY3QuYXNzaWduKHtcbiAgICBbYCR7cGtnLm5hbWV9YF06IHBrZy52ZXJzaW9uLFxuICAgIFtgJHtvcy5wbGF0Zm9ybSgpfWBdOiBvcy5yZWxlYXNlKCksXG4gICAgbm9kZTogcHJvY2Vzcy52ZXJzaW9uLnJlcGxhY2UoJ3YnLCAnJyksXG4gIH0sIGFkZG9ucyk7XG4gIHJldHVybiBPYmplY3Qua2V5cyhpZGVudGlmaWVyTWFwKS5yZWR1Y2UoKGFjYywgaykgPT4gYCR7YWNjfSAke2VzY2FwZShrKX0vJHtlc2NhcGUoaWRlbnRpZmllck1hcFtrXSl9YCwgJycpO1xufVxuIl19
exports.packageIdentifier = packageIdentifier;
/**
* Tests a "thing" for being falsy. See: https://developer.mozilla.org/en-US/docs/Glossary/Falsy
*
* @param x - The "thing" whose falsy-ness to test.
*/
function isFalsy(x) {
// NOTE: there's no way to type `x is NaN` currently (as of TypeScript v3.5)
return x === 0 || x === '' || x === null || x === undefined || (typeof x === 'number' && isNaN(x));
}
exports.isFalsy = isFalsy;
//# sourceMappingURL=util.js.map
{
"name": "@slack/interactive-messages",
"version": "1.1.1",
"description": "Slack Interactive Messages module",
"version": "1.2.0",
"description": "Official library for using the Slack Platform's Interactive Buttons, Menus, Dialogs, Actions, and Block Actions",
"author": "Slack Technologies, Inc.",
"license": "MIT",
"keywords": [
"slack",
"interactive",
"interactive-messages",
"interactive-components",
"dialog",
"button",
"menu",
"action",
"block-kit",
"block-actions",
"bot",
"server",
"http",
"api",
"verify",
"signature",
"request-signing"
],
"main": "dist/index.js",
"repository": "https://github.com/slackapi/node-slack-interactive-messages.git",
"files": [
"dist/**/*"
],
"engines": {
"node": ">=4.2.0"
},
"files": [
"dist/**/*"
],
"author": "Ankur Oberoi <aoberoi@gmail.com>",
"license": "MIT",
"repository": "slackapi/node-slack-sdk",
"homepage": "https://slack.dev/node-slack-sdk/interactive-messages",
"publishConfig": {
"access": "public"
},
"bugs": {
"url": "https://github.com/slackapi/node-slack-sdk/issues"
},
"scripts": {
"lint": "eslint src",
"lint:test": "eslint test",
"test:nyc": "nyc --reporter=html mocha test/**/*.js",
"test": "npm run lint && npm run build && npm run lint:test && npm run test:nyc",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"build": "babel src -d dist --source-maps both",
"build:docs": "jsdoc2md src/adapter.js src/index.js > docs/reference.md --separators",
"prepare": "npm run build"
"prepare": "npm run build",
"build": "npm run build:clean && tsc",
"build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output",
"lint": "tslint --project .",
"test": "nyc mocha --config .mocharc.json src/*.spec.js",
"coverage": "codecov -F interactivemessages --root=$PWD"
},
"dependencies": {
"@types/debug": "^4.1.4",
"@types/express": "^4.17.0",
"@types/lodash.isfunction": "^3.0.6",
"@types/lodash.isregexp": "^4.0.6",
"@types/lodash.isstring": "^4.0.6",
"@types/node": ">=4.2.0",
"axios": "^0.18.0",

@@ -36,21 +66,23 @@ "debug": "^3.1.0",

"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "^8.2.2",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-preset-env": "^1.6.1",
"@types/lodash.isplainobject": "^4.0.6",
"body-parser": "^1.18.2",
"chai": "^4.1.2",
"chai": "^4.2.0",
"codecov": "^3.0.0",
"eslint": "^4.9.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.7.0",
"estraverse": "^4.2.0",
"get-random-port": "0.0.1",
"jsdoc-to-markdown": "^4.0.1",
"mocha": "^5.0.5",
"jsdoc-to-markdown": "^5.0.0",
"mocha": "^6.1.4",
"nop": "^1.0.0",
"nyc": "^11.6.0",
"nyc": "^14.1.1",
"proxyquire": "^2.0.1",
"sinon": "^4.5.0"
}
"shx": "^0.3.2",
"sinon": "^4.5.0",
"source-map-support": "^0.5.12",
"ts-node": "^8.2.0",
"tslint": "^5.17.0",
"tslint-config-airbnb": "^5.11.1",
"typescript": "^3.5.1"
},
"gitHead": "35b40af1dc28ef40cb00eb9a47f902e73c27b03f"
}
# Slack Interactive Messages for Node
[![Build Status](https://travis-ci.org/slackapi/node-slack-interactive-messages.svg?branch=master)](https://travis-ci.org/slackapi/node-slack-interactive-messages)
[![codecov](https://codecov.io/gh/slackapi/node-slack-interactive-messages/branch/master/graph/badge.svg)](https://codecov.io/gh/slackapi/node-slack-interactive-messages)
<!-- TODO: per-job badge https://github.com/bjfish/travis-matrix-badges/issues/4 -->
[![Build Status](https://travis-ci.org/slackapi/node-slack-sdk.svg?branch=master)](https://travis-ci.org/slackapi/node-slack-sdk)
<!-- TODO: per-flag badge https://docs.codecov.io/docs/flags#section-flag-badges-and-graphs -->
[![codecov](https://codecov.io/gh/slackapi/node-slack-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/slackapi/node-slack-sdk)
<!-- TODO: npm versions with scoped packages: https://github.com/rvagg/nodei.co/issues/24 -->
Build your Slack Apps with rich and engaging user interactions using block actions, buttons, menus,
and dialogs. The package will help you start with sensible and secure defaults.
The `@slack/interactive-messages` helps your app respond to interactions from Slack's
[interactive messages](https://api.slack.com/messaging/interactivity), [actions](https://api.slack.com/actions), and [dialogs](https://api.slack.com/dialogs). This package will help you start with convenient and secure defaults.
The adapter gives you a meaningful API to handle actions from all of Slack's interactive
message components
[actions block elements](https://api.slack.com/reference/messaging/interactive-components),
([buttons](https://api.slack.com/docs/message-buttons),
[menus](https://api.slack.com/docs/message-menus),
and [dialogs](https://api.slack.com/dialogs)). Use it as an independent HTTP server or plug it into
an existing server as [Express](http://expressjs.com/) middleware.
This package does **not** help you compose messages with action blocks, buttons, menus and dialogs
to trigger the actions. We recommend using the
[Block Kit Builder](https://api.slack.com/tools/block-kit-builder) to design interactive Block Kit
messages and the [Message Builder](https://api.slack.com/docs/messages/builder) to design legacy
interactive messages. You can send these messages to Slack using the Web API, Incoming Webhooks,
and other parts of the platform.
* [Installation](#installation)
* [Configuration](#configuration)
* [Usage](#usage)
* [Examples](#examples)
* [Reference Documentation](#reference-documentation)
* [Support](#support)
---
## Installation
```shell
$ npm install @slack/interactive-messages
```
$ npm install --save @slack/interactive-messages
```
## Configuration
<!-- START: Remove before copying into the docs directory -->
Get started by [creating a Slack App](https://api.slack.com/apps/new) if you haven't already.
On the **Basic Information** page, in the section for **App Credentials**, note the
**Signing Secret**. You will need it to initialize the adapter.
## Usage
> ⚠️ As of `v1.0.0`, the interactive message adapter no longer accepts legacy verification tokens. You must pass a signing secret [to verify requests from Slack](https://api.slack.com/docs/verifying-requests-from-slack).
These examples show how to get started using the most common features. You'll find even more extensive [documentation on
the package's website](https://slack.dev/node-slack-sdk/interactive-messages).
Select the **Interactive Components** feature, and enable it. Input a **Request URL**. If your
app will use dynamic message menus, you also need to input a **Options Load URL**.
<!-- END: Remove before copying into the docs directory -->
![Configuring a request URL](support/interactive-components.gif)
Before building an app, you'll need to [create a Slack app](https://api.slack.com/apps/new) and install it to your
development workspace. You'll also **need a public URL** where the app can begin receiving actions. Finally, you'll need
to find the **request signing secret** given to you by Slack under the "Basic Information" of your app configuration.
<details>
<summary><strong>What's a request URL? How can I get one for development?</strong></summary>
It may be helpful to read the tutorials on [getting started](https://slack.dev/node-slack-sdk/getting-started) and
[getting a public URL that can be used for development](https://slack.dev/node-slack-sdk/tutorials/local-development).
Slack will send requests to your app server each time a button is clicked, a menu item is selected,
a dialog is submitted, and more. In order to reach your server, you have to tell Slack where your
app is listening for those requests. This location is the request URL.
---
If you're just getting started with development, you may not have a publicly accessible URL for
your app. We recommend using a development proxy, such as [ngrok](https://ngrok.com/) or
[localtunnel](https://localtunnel.github.io/www/), to generate a URL that can forward requests to
your local machine. Once you've installed the development proxy of your choice, run it to begin
forwarding requests to a specific port (for example, 3000).
### Initialize the message adapter
> ngrok: `ngrok http 3000`
The package exports a `createMessageAdapter()` function, which returns an instance of the `SlackMessageAdapter` class.
The function requires one parameter, the **request signing secret**, which it uses to enforce that all events are coming
from Slack to keep your app secure.
> localtunnel: `lt --port 3000`
```javascript
const { createMessageAdapter } = require('@slack/interactive-messages');
![Starting a development proxy](support/ngrok.gif)
// Read the signing secret from the environment variables
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
The output should show you a newly generated URL that you can use (ngrok will actually show you two
and we recommend the one that begins with "https"). Let's call this the base URL (for example,
`https://e0e88971.ngrok.io`)
// Initialize
const slackInteractions = createMessageAdapter(slackSigningSecret);
```
To create the request URL, we add the path where our app listens for message actions onto the end of
the base URL. If you are using the built-in HTTP server it is set to `/slack/actions`. In this
example the request URL would be `https://e0e88971.ngrok.io/slack/actions`. If you are using the
Express middlware, you can set whichever path you like, just remember to make the path you mount the
middleware into the application the same as the one you configure in Slack.
</details>
---
## Usage
### Start a server
### Starting a server
The message adapter transforms incoming HTTP requests into verified and parsed actions, and dispatches actions to the
appropriate handler. That means, in order for it dispatch actions for your app, it needs an HTTP server. The adapter can
receive requests from an existing server, or as a convenience, it can create and start the server for you.
The adapter needs to be attached to an HTTP server. Either use the built-in HTTP server or attach
the adapter to an existing Express application as a middleware.
In the following example, the message adapter starts an HTTP server using the `.start()` method. Starting the server
requires a `port` for it to listen on. This method returns a `Promise` which resolves for an instance of an [HTTP
server](https://nodejs.org/api/http.html#http_class_http_server) once it's ready to emit events. By default, the
built-in server will be listening for events on the path `/slack/actions`, so make sure your Request URL ends with this
path.
#### Built-in HTTP server
```javascript
// Import dependencies
const { createMessageAdapter } = require('@slack/interactive-messages');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackInteractions = createMessageAdapter(slackSigningSecret);
// Create the adapter using the app's signing secret, read from environment variable
const slackInteractions = createMessageAdapter(process.env.SLACK_SIGNING_SECRET);
// Select a port for the server to listen on.
// NOTE: When using ngrok or localtunnel locally, choose the same port it was started with.
// Read the port from the environment variables, fallback to 3000 default.
const port = process.env.PORT || 3000;
// Start the built-in HTTP server
slackInteractions.start(port).then(() => {
console.log(`server listening on port ${port}`);
});
(async () => {
// Start the built-in server
const server = await slackInteractions.start(port);
// Log a message when the server is ready
console.log(`Listening for events on ${server.address().port}`);
})();
```
#### Express application server
**Note**: To gracefully stop the server, there's also the `.stop()` method, which returns a `Promise` that resolves
when the server is no longer listening.
<details>
<summary markdown="span">
<strong><i>Using an existing HTTP server</i></strong>
</summary>
The message adapter can receive requests from an existing Node HTTP server. You still need to specify a port, but this
time its only given to the server. Starting a server in this manner means it is listening to requests on all paths; as
long as the Request URL is routed to this port, the adapter will receive the requests.
```javascript
// Import dependencies
const { createServer } = require('http');
const { createMessageAdapter } = require('@slack/interactive-messages');
const http = require('http');
const express = require('express');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackInteractions = createMessageAdapter(slackSigningSecret);
// Create the adapter using the app's signing secret, read from environment variable
const slackInteractions = createMessageAdapter(process.env.SLACK_SIGNING_SECRET);
// Read the port from the environment variables, fallback to 3000 default.
const port = process.env.PORT || 3000;
// Initialize an Express application
// NOTE: You must use a body parser for the urlencoded format before attaching the adapter
const app = express();
// Initialize a server using the message adapter's request listener
const server = createServer(slackInteractions.requestListener());
// Attach the adapter to the Express application as a middleware
// NOTE: The path must match the Request URL and/or Options URL configured in Slack
app.use('/slack/actions', slackInteractions.expressMiddleware());
// Select a port for the server to listen on.
// NOTE: When using ngrok or localtunnel locally, choose the same port it was started with.
const port = process.env.PORT || 3000;
// Start the express application server
http.createServer(app).listen(port, () => {
console.log(`server listening on port ${port}`);
server.listen(port, () => {
// Log a message when the server is ready
console.log(`Listening for events on ${server.address().port}`);
});
```
> ⚠️ As of `v1.0.0`, the interactive message adapter parses raw request bodies while performing request signing verification. This means developers no longer need to use `body-parser` middleware to parse urlencoded requests.
</details>
**Pro-Tip**: You can combine this package and
[`@slack/events-api`](https://github.com/slackapi/node-slack-events-api) by attaching each to the
same Express application.
<details>
<summary markdown="span">
<strong><i>Using an Express app</i></strong>
</summary>
### Creating handlers
The message adapter can receive requests from an [Express](http://expressjs.com/) application. Instead of plugging the
adapter's request listener into a server, it's plugged into the Express `app`. With Express, `app.use()` can be used to
set which path you'd like the adapter to receive requests from. **You should be careful about one detail: if your
Express app is using the `body-parser` middleware, then the adapter can only work if it comes _before_ the body parser
in the middleware stack.** If you accidentally allow the body to be parsed before the adapter receives it, the adapter
will emit an error, and respond to requests with a status code of `500`.
When a user interacts with one of the interactive components, this adapter will run a handler
function in response. Your app should create a handler for each type of interaction it expects.
There are two categories of interactions: **actions** and **options requests**. With either kind,
your app can describe which handler to run using one or many **constraints**.
```javascript
const { createServer } = require('http');
const express = require('express');
const bodyParser = require('body-parser');
const { createMessageAdapter } = require('@slack/interactive-messages');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const port = process.env.PORT || 3000;
const slackInteractions = createMessageAdapter(slackSigningSecret);
#### Action matching
// Create an express application
const app = express();
Use a string or RegExp as the first argument to the `.action()` method to use a `callback_id`
constraint for the handler.
// Plug the adapter in as a middleware
app.use('/my/path', slackInteractions.requestListener());
```javascript
// Run handlerFunction for any interactions from messages with a callback_id of welcome_button
slackInteractions.action('welcome_button', handlerFunction);
// Example: If you're using a body parser, always put it after the message adapter in the middleware stack
app.use(bodyParser());
// Run handlerFunction for any interactions from messages with a callback_id that match the RegExp
slackInteractions.action(/welcome_(\w+)/, handlerFunction);
// This function is discussed in "Responding to actions" below
function handlerFunction() {
}
// Initialize a server for the express app - you can skip this and the rest if you prefer to use app.listen()
const server = createServer(app);
server.listen(port, () => {
// Log a message when the server is ready
console.log(`Listening for events on ${server.address().port}`);
});
```
> ⚠️ If your app is using [blocks](https://api.slack.com/messaging/composing/layouts), you _must_
use an object to describe constraints (`block_id` and `action_id`). Blocks have no `callback_id`.
There are examples below.
</details>
Use an object to describe other constraints, even combine multiple constraints to create more
specific handlers. The full set of constraint options are described in the
[reference documentation](docs/reference.md#module_adapter--module.exports..SlackMessageAdapter+action).
---
```javascript
// Run handlerFunction for all button presses
slackInteractions.action({ type: 'button' }, handlerFunction)
### Handle an action
// Run handlerFunction for the dialog submission with callback_id of 'welcome'
slackInteractions.action({ callbackId: 'welcome', type: 'dialog_submission' }, handlerFunction);
Actions are interactions in Slack that generate an HTTP request to your app. These are:
// Run handlerFunction for a message action with callback_id of 'save'
slackInteractions.action({ callbackId: 'save', type: 'message_action' }, handlerFunction);
- **Block actions**: A user interacted with one of the [interactive
components](https://api.slack.com/reference/messaging/interactive-components) in a message built with [block
elements](https://api.slack.com/reference/messaging/block-elements).
- **Message actions**: A user selected an [action in the overflow menu of a message](https://api.slack.com/actions).
- **Dialog submission**: A user submitted a form in a [modal dialog](https://api.slack.com/dialogs)
- **Attachment actions**: A user clicked a button or selected an item in a menu in a message built with [legacy message
attachments](https://api.slack.com/interactive-messages).
// Run handlerFunction for all menu selections inside an unfurl attachment
slackInteractions.action({ unfurl: true, type: 'select' }, handlerFunction);
You app will only handle actions that occur in messages or dialogs your app produced. [Block Kit
Builder](https://api.slack.com/tools/block-kit-builder) is a playground where you can prototype your interactive
components with block elements.
// This function is discussed in "Responding to actions" below
function handlerFunction() {
}
```
Apps register functions, called **handlers**, to be triggered when an action is received by the adapter using the
`.action(constraints, handler)` method. When registering a handler, you describe which action(s) you'd like the handler
to match using **constraints**. Constraints are [described in detail](#constraints) below. The adapter will call the
handler whose constraints match the action best.
#### Action matching with blocks
These handlers receive up to two arguments:
Apps using [blocks](https://api.slack.com/messaging/composing/layouts) must use an object to
describe constraints since they contain no `callback_id`. Instead, you can use the `block_id` and
`action_id` described in the [interactive component documentation](https://api.slack.com/reference/messaging/interactive-components).
1. `payload`: An object whose contents describe the interaction that occurred. Use the links above as a guide for the
shape of this object (depending on which kind of action you expect to be handling).
2. `respond(...)`: A function used to follow up on the action after the 3 second limit. This is used to send an
additional message (`in_channel` or `ephemeral`, `replace_original` or not) after some deferred work. This can be
used up to 5 times within 30 minutes.
```javascript
// Run handlerFunction for a any interactions with a block_id of 'save'
slackInteractions.action({ blockId: 'save' }, handlerFunction);
Handlers can return an object, or a `Promise` for a object which must resolve within the `syncResponseTimeout` (default:
2500ms). The contents of the object depend on the kind of action that's being handled.
// Run handlerFunction for a any interactions with an action_id of 'select_coffee'
slackInteractions.action({ actionId: 'select_coffee' }, handlerFunction);
- **Attachment actions**: The object describes a message to replace the message where the interaction occurred. **It's
recommended to remove interactive elements when you only expect the action once, so that no other users might trigger
a duplicate.** If no value is returned, then the message remains the same.
- **Dialog submission**: The object describes [validation errors](https://api.slack.com/dialogs#input_validation) to
show the user and prevent the dialog from closing. If no value is returned, the submission is treated as successful.
- **Block actions** and **Message actions**: Avoid returning any value.
// Run handlerFunction for a static select element with an action_id of 'select_animal'
slackInteractions.action({ actionId: 'select_animal', type: 'static_select' }, handlerFunction);
```javascript
const { createMessageAdapter } = require('@slack/interactive-messages');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackInteractions = createMessageAdapter(slackSigningSecret);
const port = process.env.PORT || 3000;
// This function is discussed in "Responding to actions" below
function handlerFunction() {
}
```
// Example of handling static select (a type of block action)
slackInteractions.action({ type: 'static_select' }, (payload, respond) => {
// Logs the contents of the action to the console
console.log('payload', payload);
Blocks are not supported in dialogs or message actions, so you cannot filter by those types.
// Send an additional message to the whole channel
doWork()
.then(() => {
respond({ text: 'Thanks for your submission.' });
})
.catch((error) => {
respond({ text: 'Sorry, there\'s been an error. Try again later.' });
});
#### Responding to actions
// If you'd like to replace the original message, use `chat.update`.
// Not returning any value.
});
Slack requires your app to respond to actions in a timely manner so that the user isn't blocked.
The adapter helps your app respond correctly and on time.
// Example of handling all message actions
slackInteractions.action({ type: 'message_action' }, (payload, respond) => {
// Logs the contents of the action to the console
console.log('payload', payload);
For most actions (block actions, button presses, menu selections, and message actions), a response
is simply an updated message to replace the one where the interaction occurred. Your app can return
a message (or a Promise for a message) from the handler. **We recommend that apps at least remove
the interactive elements from the message in the response** so that users don't get confused (for
example, click the same button twice). Find details about the format for a message in the docs for
[message](https://api.slack.com/docs/interactive-message-field-guide#message).
// Send an additional message only to the user who made interacted, as an ephemeral message
doWork()
.then(() => {
respond({ text: 'Thanks for your submission.', response_type: 'ephemeral' });
})
.catch((error) => {
respond({ text: 'Sorry, there\'s been an error. Try again later.', response_type: 'ephemeral' });
});
The handler will receive a `payload` which describes the interaction a user had with a message.
Find more details about the structure of `payload` in the docs for
[buttons](https://api.slack.com/docs/message-buttons#responding_to_message_actions) and
[menus](https://api.slack.com/docs/message-menus#request_url_response).
// If you'd like to replace the original message, use `chat.update`.
// Not returning any value.
});
> ⚠️ If your app is using [blocks](https://api.slack.com/messaging/composing/layouts), your payload
will look different. You can find the payload for action block interactivity on the
[interactive components](https://api.slack.com/reference/messaging/interactive-components) page.
// Example of handling all dialog submissions
slackInteractions.action({ type: 'dialog_submission' }, (payload, respond) => {
// Validate the submission (errors is of the shape in https://api.slack.com/dialogs#input_validation)
const errors = validate(payload.submission);
If your app defers some work asynchronously (like querying another API or using a database), you can
continue to update the message using the `respond()` function that is provided to your handler.
// Only return a value if there were errors
if (errors) {
return errors;
}
**Example button handler:**
// Send an additional message only to the use who made the submission, as an ephemeral message
doWork()
.then(() => {
respond({ text: 'Thanks for your submission.', response_type: 'ephemeral' });
})
.catch((error) => {
respond({ text: 'Sorry, there\'s been an error. Try again later.', response_type: 'ephemeral' });
});
});
```javascript
slackInteractions.action('welcome_agree_button', (payload, respond) => {
// `payload` is an object that describes the interaction
console.log(`The user ${payload.user.name} in team ${payload.team.domain} pressed a button`);
// Example of handling attachment actions. This is for button click, but menu selection would use `type: 'select'`.
slackInteractions.action({ type: 'button' }, (payload, respond) => {
// Logs the contents of the action to the console
console.log('payload', payload);
// Your app does some work using information in the payload
users.findBySlackId(payload.user.id)
.then(user => user.acceptPolicyAndSave())
// Replace the original message again after the deferred work completes.
doWork()
.then(() => {
// After the asynchronous work is done, call `respond()` with a message object to update the
// message.
const message = {
text: 'Thank you for agreeing to the team\'s policy.',
};
respond(message);
respond({ text: 'Processing complete.', replace_original: true });
})
.catch((error) => {
// Handle errors
console.error(error);
respond({
text: 'An error occurred while recording your agreement. Please contact an admin.'
});
respond({ text: 'Sorry, there\'s been an error. Try again later.', replace_original: true });
});
// Before the work completes, return a message object that is the same as the original but with
// the interactive elements removed.
const reply = payload.original_message;
delete reply.attachments[0].actions;
return reply;
// Return a replacement message
return { text: 'Processing...' };
});
(async () => {
const server = await slackInteractions.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
```
**NOTE:** If you don't return any value, the adapter will respond with an OK response on your app's
behalf, which results in the message staying the same. If you return a Promise, and it resolves
after the timeout (2.5 seconds), then the adapter will also respond with an OK response and later
call `respond()` with the eventual value. If you choose to use a Promise, remember to add a
`.catch()` to handle rejections.
---
Dialog submission action handlers respond slightly differently from button presses and menu
selections.
### Handle an options request
The handler will receive a `payload` which describes all the elements in the dialog. Find more
details about the structure of `payload` in the docs for
[dialogs](https://api.slack.com/dialogs#evaluating_submission_responses).
Options requests are generated when a user interacts with a menu that uses a dynamic data source. These menus can be
inside a block element, an attachment, or a dialog. In order for an app to use a dynamic data source, you must save an
"Options Load URL" in the app configuration.
Unlike with buttons and menus, the response does not replace the message (a dialog is not a message)
but rather the response tells Slack whether the inputs are valid and the dialog can be closed on
the user's screen. Your app returns an object with a list of errors as a property (or a Promise for an object with a list of errors as a property) from the
handler. If there are no errors, your app should return nothing from the handler. Find more details
on the structure of the list of errors in the docs for
[input validation](https://api.slack.com/dialogs#input_validation).
Apps register functions, called **handlers**, to be triggered when an options request is received by the adapter using
the `.options(constraints, handler)` method. When registering a handler, you describe which options request(s) you'd
like the handler to match using **constraints**. Constraints are [described in detail](#constraints) below. The adapter
will call the handler whose constraints match the action best.
The handler will also receive a `respond()` function, which can be used to send a message to the
conversation where the dialog was triggered. **We recommend that apps use `respond()` to notify the
user that the dialog submission was received** and use it again to communicate updates such as
success or failure.
These handlers receive a single `payload` argument. The `payload` describes the interaction with the menu that occurred.
The exact shape depends on whether the interaction occurred within a [block
element](https://api.slack.com/reference/messaging/block-elements#external-select),
[attachment](https://api.slack.com/docs/message-menus#options_load_url), or a
[dialog](https://api.slack.com/dialogs#dynamic_select_elements_external).
**Example dialog submission handler:**
Handlers can return an object, or a `Promise` for a object which must resolve within the `syncResponseTimeout` (default:
2500ms). The contents of the object depend on where the options request was generated (you can find the expected shapes
in the preceding links).
```javascript
slackInteractions.action('create_order_dialog', (payload, respond) => {
// `payload` is an object that describes the interaction
console.log(`The user ${payload.user.name} in team ${payload.team.domain} submitted a dialog`);
const { createMessageAdapter } = require('@slack/interactive-messages');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackInteractions = createMessageAdapter(slackSigningSecret);
const port = process.env.PORT || 3000;
// Check the values in `payload.submission` and report any possible errors
// in the format {errors: [{name:'username', error:'Uh-oh. This username has been taken!'}]}
const errors = validateOrderSubmission(payload.submission);
if (errors) {
return errors;
} else {
setTimeout(() => {
// When there are no errors, after this function returns, send an acknowledgement to the user
respond({
text: `Thank you for completing an order, <@${payload.user.id}>. ` +
'Your order number will appear here shortly.',
});
// Example of handling options request within block elements
slackInteractions.options({ within: 'block_actions' }, (payload) => {
// Return a list of options to be shown to the user
return {
options: [
{
text: {
type: 'plain_text',
text: 'A good choice',
},
value: 'good_choice',
},
],
};
});
// Your app does some work using information in the submission
orders.create(payload.submission)
.then((order) => {
// After the asynchronous work is done, call `respond()` with a message object to update the
// message.
const message = {
text: `Thank you for completing an order, <@${payload.user.id}>. ` +
`Your order number is ${order.number}`,
};
respond(message);
})
.catch((error) => {
// Handle errors
console.error(error);
respond({ text: 'An error occurred while creating your order.' });
});
});
}
// Example of handling options request within attachments
slackInteractions.options({ within: 'interactive_message' }, (payload) => {
// Return a list of options to be shown to the user
return {
options: [
{
text: 'A decent choice',
value: 'decent_choice',
},
],
};
});
```
**NOTE:** If you return a Promise which takes longer than the timeout (2.5 seconds) to complete, the
adapter will continue to wait and the user will see an error.
// Example of handling options request within dialogs
slackInteractions.options({ within: 'dialog' }, (payload) => {
// Return a list of options to be shown to the user
return {
options: [
{
label: 'A choice',
value: 'choice',
},
],
};
});
#### Options request matching
(async () => {
const server = await slackInteractions.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
```
Use a string or RegExp as the first argument to the `.options()` method to use a `callback_id`
constraint for the handler.
---
```javascript
// Run handlerFunction for any options requests from messages with a callback_id of project_menu
slackInteractions.options('project_menu', handlerFunction);
### Constraints
// Run handlerFunction for any options requests from messages with a callback_id that match the RegExp
slackInteractions.options(/(\w+)_menu/, handlerFunction);
Constraints allow you to describe when a handler should be called. In simpler apps, you can use very simple constraints
to divide up the structure of your app. In more complex apps, you can use specific constraints to target very specific
conditions, and express a more nuanced structure of your app.
// This function is discussed in "Responding to options requests" below
function handlerFunction() {
}
```
Constraints can be a simple string, a `RegExp`, or an object with a number of properties.
> ⚠️ If your app is using [blocks](https://api.slack.com/messaging/composing/layouts), you _must_
use an object to describe contraints (`block_id` and `action_id`). Blocks have no `callback_id`.
There are examples below.
| Property name | Type | Description | Used with `.actions()` | Used with `.options()` |
|---------------|------|-------------|------------------------|------------------------|
| `callbackId` | `string` or `RegExp` | Match the `callback_id` for attachment or dialog | ✅ | ✅ |
| `blockId` | `string` or `RegExp` | Match the `block_id` for a block action | ✅ | ✅ |
| `actionId` | `string` or `RegExp` | Match the `action_id` for a block action | ✅ | ✅ |
| `type` | any block action element type or `message_actions` or `dialog_submission` or `button` or `select` | Match the kind of interaction | ✅ | 🚫 |
| `within` | `block_actions` or `interactive_message` or `dialog` | Match the source of options request | 🚫 | ✅ |
| `unfurl` | `boolean` | Whether or not the `button`, `select`, or `block_action` occurred in an App Unfurl | ✅ | 🚫 |
Use an object to describe other constraints, even combine multiple constraints to create more
specific handlers. The full set of constraint options are described in the
[reference documentation](docs/reference.md#module_adapter--module.exports..SlackMessageAdapter+options).
All of the properties are optional, its just a matter of how specific you want to the handler's behavior to be. A
`string` or `RegExp` is a shorthand for only specifying the `callbackId` constraint. Here are some examples:
```javascript
// Run handlerFunction for all options requests from inside a dialog
slackInteractions.options({ within: 'dialog' }, handlerFunction)
const { createMessageAdapter } = require('@slack/interactive-messages');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackInteractions = createMessageAdapter(slackSigningSecret);
const port = process.env.PORT || 3000;
// Run handlerFunction for all options requests from inside a message with callback_id of 'project_menu'
slackInteractions.options({ callbackId: 'project_menu', within: 'interactive_message' }, handlerFunction)
// Example of constraints for an attachment action with a callback_id
slackInteractions.action('new_order', (payload, respond) => { /* ... */ });
// This function is discussed in "Responding to options requests" below
function handlerFunction() {
}
```
// Example of constraints for a block action with an action_id
slackInteractions.action({ actionId: 'new_order' }, (payload, respond) => { /* ... */ });
#### Options request matching with blocks
// Example of constraints for an attachment action with a callback_id pattern
slackInteractions.action(/order_.*/, (payload, respond) => { /* ... */ });
Use an object to describe constraints if your app is using blocks.
// Example of constraints for a block action with a callback_id pattern
slackInteractions.action({ actionId: /order_.*/ }, (payload, respond) => { /* ... */ });
```javascript
// Run handlerFunction for all options requests from inside a message with blocks
slackInteractions.options({ within: 'block_actions' }, handlerFunction)
// Example of constraints for an options request with a callback_id and within a dialog
slackInteractions.options({ within: 'dialog', callbackId: 'model_name' }, (payload) => { /* ... */ });
// Run handlerFunction for all options requests from inside a message with blocks with block_id of 'coffee_menu'
slackInteractions.options({ blockId: 'coffee_menu', within: 'block_actions' }, handlerFunction)
// Example of constraints for all actions.
slackInteractions.action({}, (payload, respond) => { /* ... */ });
// This function is discussed in "Responding to options requests" below
function handlerFunction() {
}
(async () => {
const server = await slackInteractions.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
```
#### Responding to options requests
---
Slack requires your app to respond to options requests in a timely manner so that the user isn't
blocked. The adapter helps your app respond correctly and on time.
### Debugging
A response is a list of options or option groups that your app wants to populate into the menu.
Your app will return the list (or a Promise for the list) from the handler. However, if you use
a Promise which takes longer than the timeout (2.5 seconds) to resolve, the adapter will continue to
wait and the user will see an error. Find details on formatting the list in the docs for
[options fields](https://api.slack.com/docs/interactive-message-field-guide#option_fields) and
[options groups](https://api.slack.com/docs/interactive-message-field-guide#option_groups).
If you're having difficulty understanding why a certain request received a certain response, you can try debugging your
program. A common cause is a request signature verification failing, sometimes because the wrong secret was used. The
following example shows how you might figure this out using debugging.
The handler will receive a `payload` which describes the current state of the menu. If the user
is typing into the field, the `payload.value` property contains the value they have typed so far.
Find more details about the structure of `payload` in the docs for
[dynamic menus](https://api.slack.com/docs/message-menus#options_load_url).
Start your program with the `DEBUG` environment variable set to `@slack/interactive-messages:*`. This should only be
used for development/debugging purposes, and should not be turned on in production. This tells the adapter to write
messages about what its doing to the console. The easiest way to set this environment variable is to prepend it to the
`node` command when you start the program.
**Example options request handler:**
```shell
$ DEBUG=@slack/interactive-messages:* node app.js
```
`app.js`:
```javascript
slackInteractions.options('project_menu', (payload) => {
// `payload` is an object that describes the interaction
console.log(`The user ${payload.user.name} in team ${payload.team.domain} is typing in a menu`);
const { createMessageAdapter } = require('@slack/interactive-messages');
const port = process.env.PORT || 3000;
// Your app gathers possible completions using the user's input
return projects.fuzzyFind(payload.value)
// Format the data as a list of options (or options groups)
.then(formatProjectsAsOptions)
.catch(error => {
// Handle errors
console.error(error)
return { options: [] };
});
// Oops! Wrong signing secret
const slackInteractions = createMessageAdapter('not a real signing secret');
slackInteractions.action({ action_id: 'welcome_agree_button' }, (payload) => {
/* Not shown: Record user agreement to database... */
return {
text: 'Thanks!',
};
});
(async () => {
const server = await slackInteractions.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
```
**NOTE:** Options request responses vary slightly depending on whether the menu is within a
message or within a dialog. When the options request is from within a menu, the fields for each
option are `text` and `value`. When the options request is from within a dialog, the fields for each
option are `label` and `value`.
When the adapter receives a request, it will now output something like the following to the console:
#### Chaining
```
@slack/interactive-messages:adapter instantiated
@slack/interactive-messages:adapter server created - path: /slack/actions
@slack/interactive-messages:adapter server started - port: 3000
@slack/interactive-messages:http-handler request received - method: POST, path: /slack/actions
@slack/interactive-messages:http-handler request signature is not valid
@slack/interactive-messages:http-handler handling error - message: Slack request signing verification failed, code: SLACKHTTPHANDLER_REQUEST_SIGNATURE_VERIFICATION_FAILURE
@slack/interactive-messages:http-handler sending response - error: Slack request signing verification failed, responseOptions: {}
```
The `.action()` and `.options()` methods return the adapter object, which means the API supports
chaining.
This output tells the whole story of why the adapter responded to the request the way it did. Towards the end you can
see that the signature verification failed.
```javascript
slackInteractions
.action('make_order_1', orderStepOne)
.action('make_order_2', orderStepTwo)
.action('make_order_3', orderStepThree)
.options('make_order_3', orderStepThreeOptions);
```
If you believe the adapter is behaving incorrectly, before filing a bug please gather the output from debugging and
include it in your bug report.
---
### More
The [documentation website](https://slack.dev/node-slack-sdk/interactive-messages) has information about these
additional features of the `SlackMessageAdapter`:
* Custom timeout on handler returned `Promise`s
* Opt out of late response fallback
---
## Examples
* [Express All Interactions](examples/express-all-interactions) - A ready to run sample app that
creates and responds to buttons, menus, and dialogs. It also demonstrates a menu with dynamic
options. It is built on top of the [Express](https://expressjs.com) web framework.
* [Express All Interactions](../../examples/express-all-interactions) - A ready to run sample app that creates and responds
to buttons, menus, and dialogs. It also demonstrates a menu with dynamic options. It is built on top of the
[Express](https://expressjs.com) web framework.
## Reference Documentation
---
See the [reference documentation](docs/reference.md) a more formal description of this pacakge's
objects and functions.
## Requirements
## Support
This package supports Node v8 LTS and higher. It's highly recommended to use [the latest LTS version of
node](https://github.com/nodejs/Release#release-schedule), and the documentation is written using syntax and features
from that version.
Need help? Join the [Bot Developer Hangout](https://community.botkit.ai/) team and talk to us in
[#slack-api](https://dev4slack.slack.com/messages/slack-api/).
## Getting Help
You can also [create an Issue](https://github.com/slackapi/node-slack-events-api/issues/new)
right here on GitHub.
If you get stuck, we're here to help. The following are the best ways to get assistance working through your issue:
* [Issue Tracker](http://github.com/slackapi/node-slack-sdk/issues) for questions, feature requests, bug reports and
general discussion related to these packages. Try searching before you create a new issue.
* [Email us](mailto:developers@slack.com) in Slack developer support: `developers@slack.com`
* [Bot Developers Hangout](https://community.botkit.ai/): a Slack community for developers
building all types of bots. You can find the maintainers and users of these packages in **#sdk-node-slack-sdk**.

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc