@slack/interactive-messages
Advanced tools
Comparing version 1.1.1 to 1.2.0
@@ -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 |
101
dist/util.js
@@ -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" | ||
} |
711
README.md
# 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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
23
0
0
502
96959
14
19
1019
2
+ Added@types/debug@^4.1.4
+ Added@types/express@^4.17.0
+ Added@types/node@>=4.2.0
+ Added@types/body-parser@1.19.5(transitive)
+ Added@types/connect@3.4.38(transitive)
+ Added@types/debug@4.1.12(transitive)
+ Added@types/express@4.17.21(transitive)
+ Added@types/express-serve-static-core@4.19.6(transitive)
+ Added@types/http-errors@2.0.4(transitive)
+ Added@types/lodash@4.17.13(transitive)
+ Added@types/lodash.isfunction@3.0.9(transitive)
+ Added@types/lodash.isregexp@4.0.9(transitive)
+ Added@types/lodash.isstring@4.0.9(transitive)
+ Added@types/mime@1.3.5(transitive)
+ Added@types/ms@0.7.34(transitive)
+ Added@types/node@22.9.3(transitive)
+ Added@types/qs@6.9.17(transitive)
+ Added@types/range-parser@1.2.7(transitive)
+ Added@types/send@0.17.4(transitive)
+ Added@types/serve-static@1.15.7(transitive)
+ Addedundici-types@6.19.8(transitive)