Skip to content

Commit

Permalink
implements progressive message updating
Browse files Browse the repository at this point in the history
  • Loading branch information
aoberoi committed Mar 20, 2019
1 parent fade927 commit 1f214b0
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 13 deletions.
48 changes: 45 additions & 3 deletions packages/node-slack-interactive-messages/README.md
Expand Up @@ -11,10 +11,10 @@ app.use('/slack/actions', slackMessages.expressMiddleware());

// when the first param is a string, match an action by callback_id
// returns `this` so that these action registrations are chainable
slackMessages.action('welcome_button', (action) => {
slackMessages.action('welcome_button', (payload) => {
// the `action` arg is the entire payload from the HTTP request
// return a value or a Promise for a value describing a message for replacement
// if the value is falsy or promise resolves to anything falsy, do nothing (no replacement)
// if the value is falsy or promise, do nothing (no replacement)
// if the value is a JSON object, return it in the HTTP response for replacement
// if the promise resolves to a JSON object, send it for replacement using the `response_url`
// if the promise rejects or there is a synchronous throw, the response is 500 Internal Server Error
Expand All @@ -29,7 +29,7 @@ slackMessages.action(/success_\d+/, () => {});
// type: 'button'|'select',
// unfurl: boolean,
// }
slackMessages.action({ type: 'button', unfurl: true }, (action) => {
slackMessages.action({ type: 'button', unfurl: true }, (payload) => {
// if the action was an unfurl, the Promise that is returned should be for an attachment as opposed to a whole message
});

Expand All @@ -38,6 +38,48 @@ slackMessages.options('ticket_menu', (selection) => {
// return a value or a Promise for a set of options (same rules as .action())
});

// when you want to progressively update the message
slackMessage.action('analyze', (payload, respond) => {
let hasCompleted = false;

// call the respond function with a JSON object for the message
doAnalysis(payload)
.then(buildMessage)
.then(respond)
// respond will return a Promise that resolves to a response object on success
.then((response) => {
hasCompleted = true;
// response contains properties data, status, statusText, headers
console.log(response);
});
.catch((error) => {
hasCompleted = true;
const message = {
text: 'Analysis failed',
};
// error may contain a response property
if (error.response) {
message.text += '\nServer Error Status Code:' + error.response.status;
}
respond(message);
});

setTimeout(() => {
// you can use the respond function more than once
if (!hasCompleted) {
respond({
text: 'Still analyzing... :speech_balloon:'
});
}
}, 4000);

return {
text: 'Analysis in progress... :speech_balloon:'
};
});

// make if very clear that only the first matching callback gets invoked

// TODO: api to inspect action handlers? api to remove action handlers?
// TODO: do we need options for name or value matching/routing? slapp has these

Expand Down
20 changes: 12 additions & 8 deletions packages/node-slack-interactive-messages/src/adapter.js
Expand Up @@ -34,7 +34,7 @@ function formatMatchingConstraints(matchingConstraints) {
*/
function validateConstraints(matchingConstraints) {
if (matchingConstraints.callbackId &&
!(isString(matchingConstraints) || isRegExp(matchingConstraints))) {
!(isString(matchingConstraints.callbackId) || isRegExp(matchingConstraints.callbackId))) {
return new TypeError('Callback ID must be a string or RegExp');
}

Expand Down Expand Up @@ -64,7 +64,6 @@ export default class SlackMessageAdapter {
* @param {string} verificationToken - Slack app verification token used to authenticate request
*/
constructor(verificationToken) {
// TODO: need includeHeaders option?
if (!isString(verificationToken)) {
throw new TypeError('SlackMessageAdapter needs a verification token');
}
Expand Down Expand Up @@ -176,6 +175,10 @@ export default class SlackMessageAdapter {
dispatch(payload) {
const action = payload.actions && payload.actions[0];
let result = { status: 200 };
const respond = (message) => {
debug('sending async response');
return this.axios.post(payload.response_url, message);
};

this.callbacks.some(([constraints, fn]) => {
// Returning false in this function continues the iteration, and returning true ends it.
Expand Down Expand Up @@ -206,7 +209,7 @@ export default class SlackMessageAdapter {
}

try {
callbackResult = fn.call(this, payload);
callbackResult = fn.call(this, payload, respond);
} catch (error) {
result = { status: 500 };
return true;
Expand All @@ -215,17 +218,18 @@ export default class SlackMessageAdapter {
if (callbackResult) {
// Checking for Promise type
if (typeof callbackResult.then === 'function') {
callbackResult
.then(asyncResult => this.axios.post(payload.response_url, asyncResult))
.catch(error => debug('async error for callback. callback_id: %s, error: %s', payload.callback_id, error.message));
callbackResult.then(respond).catch((error) => {
debug('async error for callback. callback_id: %s, error: %s',
payload.callback_id, error.message);
});
return true;
}
result = { status: 200, content: JSON.stringify(callbackResult) };
result = { status: 200, content: callbackResult };
return true;
}
return true;
});

return result;
return Promise.resolve(result);
}
}
Expand Up @@ -47,6 +47,11 @@ export function createExpressMiddleware(adapter) {
return;
}

if (req.body.ssl_check) {
respond({ status: 200 });
return;
}

const payload = JSON.parse(req.body.payload);

// Handle request token verification
Expand Down
2 changes: 1 addition & 1 deletion packages/node-slack-interactive-messages/src/index.js
Expand Up @@ -3,6 +3,6 @@ import SlackMessageAdapter from './adapter';

export const errorCodes = middlewareErrorCodes;

export function createSlackMessageAdapter(verificationToken, options) {
export function createMessageAdapter(verificationToken, options) {
return new SlackMessageAdapter(verificationToken, options);
}
2 changes: 1 addition & 1 deletion packages/node-slack-interactive-messages/src/util.js
Expand Up @@ -16,5 +16,5 @@ export function packageIdentifier(addons = {}) {
[`${os.platform()}`]: os.release(),
node: process.version.replace('v', ''),
}, addons);
return identifierMap.keys().reduce((acc, k) => `${acc} ${escape(k)}/${escape(identifierMap[k])}`, '');
return Object.keys(identifierMap).reduce((acc, k) => `${acc} ${escape(k)}/${escape(identifierMap[k])}`, '');
}

0 comments on commit 1f214b0

Please sign in to comment.