Closed zhifengkoh closed 5 months ago
Hi @zhifengkoh, thanks for asking the question. I just quickly checked if your example app works for me, and I didn't see any issues with it (token: process.env.SLACK_BOT_TOKEN,
is unnecessary though). http://localhost:300/slack/install
services the page as I expect. I guess your ngrok might be forwarding your browser request to a different process or your changes to enable the OAuth flow might not be reflected to the running process. I hope you will figure the cause out soon.
Thanks @seratch for helping me to verify that! I will try to figure it out.
👋 It looks like this issue has been open for 30 days with no activity. We'll mark this as stale for now, and wait 10 days for an update or for further comment before closing this issue out. If you think this issue needs to be prioritized, please comment to get the thread going again! Maintainers also review issues marked as stale on a regular basis and comment or adjust status if the issue needs to be reprioritized.
As this issue has been inactive for more than one month, we will be closing it. Thank you to all the participants! If you would like to raise a related issue, please create a new issue which includes your specific details and references this issue number.
Hey @zhifengkoh, @seratch, I'm having the exact same issue, do you mind sharing how you fixed it?
@latifs I had the same issue, I realized I didn't enter clientId and clientSecret in my .env. It worked when I added those.
Encountered similar issue - missing stateSecret
.
Apologies in advance if this isn't the right place to ask, but I didn't know where else to turn to after a lot of Googling and ChatGPT/Perplexity.
I am trying to setup OAuth for my BoltJS app so that I can prepare my app for distribution on multiple workspaces. I'm not very familiar building with OAuth so the inner workings are not familiar to me. So far, I'm following along the BoltJS documentation, which has been quite easy to follow for the most part. But where the BoltJS documentation says once OAuth is enabled, I should see an automatically rendered App Install page located at the URL path
/slack/install
, my server is instead giving me a 404 error and that this path is unhandled.I don't understand what the issue could be because so far my app is very simple: it has only implemented
app.command
app.view
app.message
)Thank you in advance for your help! My app.js is appended below.
Reproducible in:
The Slack SDK version
"slack/bolt": "^3.17.1"
Node.js runtime version
v20.11.1
OS info
Steps to reproduce:
(Share the commands to run, source code, and project settings)
node app.js
http://my-domain.ngrok-free.app/slack/install
)Expected result:
I was expecting to see the default slack install webpage rendered by Bolt, as mentioned here: https://slack.dev/bolt-js/concepts#authenticating-oauth.
My app is working fine when events and user interactions (with modals, multi-select menus built in Block Kit, etc.) are sending requests to
http://my-domain.ngrok-free.app/slack/events
. So it's not an issue with my ngrok tunnel or the app itself.Actual result:
After entering
http://my-domain.ngrok-free.app/slack/install
in my browser:/slack/install
):POST /slack/events 200 OK
POST /slack/events 200 OK
GET /slack/install 404 Not Found
GET /slack/install 404 Not Found
POST /slack/events 200 OK
POST /slack/events 200 OK
const { App } = require('@slack/bolt'); const { FileInstallationStore} = require ('@slack/oauth')
const app = new App({ token: process.env.SLACK_BOT_TOKEN, signingSecret: process.env.SLACK_SIGNING_SECRET, clientId: process.env.SLACK_CLIENT_ID, clientSecret: process.env.SLACK_CLIENT_SECRET, stateSecret: 'my-secret', //Note: This should be randomly generated and stored/rotated as an environment variable in production scopes: ['chat:write', 'commands'], installationStore: new FileInstallationStore() });
//Retrieve the names of conversations or users given their Slack conversation IDs async function getNames(client, ids) { const names = [];
for (const id of ids) { try { if (id.startsWith('U')) { // Handle user IDs const result = await client.users.info({ user: id }); if (result.ok) { names.push(result.user.real_name); // or
result.user.name
for the username } else { names.push(Failed to fetch details for user ID: ${id}
); } } else { // Handle conversation IDs const result = await client.conversations.info({ channel: id }); if (result.ok) { names.push(result.channel.name); } else { names.push(Failed to fetch details for conversation ID: ${id}
); } } } catch (error) { console.error(Error fetching details for ID: ${id}
, error); names.push(Error for ID: ${id}
); } }return names; }
//Open a Modal window for the command /alertbot-create app.command('/alertbot-create', async ({ ack, body, client }) => { // Acknowledge the command request await ack(); console.log("command invoked: alertbot-create");
try { // Call views.open with the built-in client const result = await client.views.open({ // Pass a valid trigger_id within 3 seconds of receiving the command trigger_id: body.trigger_id, // View payload view: { "type": "modal", "callback_id": "alertbot_create_view", "title": { "type": "plain_text", "text": "Create a new Alert" }, "submit": { "type": "plain_text", "text": "Submit" }, "blocks": [ { "type": "input", "block_id": "search_phrase_block", "element": { "type": "plain_text_input", "action_id": "search_phrase_action", "multiline": false, "placeholder": { "type": "plain_text", "text": "The exact word or phrase that you want to listen out for" } }, "label": { "type": "plain_text", "text": "Search Phrase" } }, { "type": "input", "block_id": "alert_message_contents_block", "element": { "type": "plain_text_input", "multiline": true, "action_id": "alert_message_contents_action", "placeholder": { "type": "plain_text", "text": "The contents of your alert message, excluding users or groups" } }, "label": { "type": "plain_text", "text": "Alert Message Contents", "emoji": true } }, { "type": "input", "block_id": "recipients_block", "element": { "type": "multi_conversations_select", "placeholder": { "type": "plain_text", "text": "Select conversations" }, "filter": { "include": [ "public", "private", "im" ], "exclude_bot_users": true }, "action_id": "recipients_action", "default_to_current_conversation": true }, "label": { "type": "plain_text", "text": "Select users to @mention and/or #channels to post to" } }, { "type": "input", "block_id": "alert_configuration_block", "element": { "type": "checkboxes", "options": [ { "text": { "type": "plain_text", "text": "Send alert as DM only" }, "description": { "type": "mrkdwn", "text": "Alerts will be sent from AlertBot directly to users mentioned. Channels will be ignored." }, "value": "is_dm_only" }, { "text": { "type": "plain_text", "text": "Disable threaded alerts" }, "description": { "type": "mrkdwn", "text": "For alerts posted as a response to the trigger message in its original channel, do not reply in a thread." }, "value": "disable_threading" }, { "text": { "type": "plain_text", "text": "Disable link to trigger message" }, "description": { "type": "mrkdwn", "text": "For alerts posted elsewhere besides the original channel, do not include a link to the triggering message." }, "value": "disable_link_to_trigger" } ], "action_id": "alert_configuration_action" }, "label": { "type": "plain_text", "text": "Alert configuration", "emoji": true } } ] } }); } catch (error) { console.error(error); } });
//Listen to user selection of @users or #channels in the multi conversations select menu of the alertbot-create modal app.action('multi_conversations_select-action', async({ack, body, client}) => { await ack(); console.log("action captured: multi_conversations_select-action");
});
//Listen to the Modal window submission for /alertbot-create app.view('alertbot_create_view', async ({ ack, body, view, client}) => { console.log("view submitted: alertbot_create_view");
// Check if at least one conversation is selected in the recipients_action multi conversations select menu const selectedConversations = view.state.values.recipients_block.recipients_action.selected_conversations; if (!selectedConversations || selectedConversations.length === 0) { // Acknowledge the view submission with an error if no conversations are selected return await ack({ response_action: 'errors', errors: { recipients_action: 'Please select at least one conversation.' } }); }
// Acknowledge the view submission event await ack();
const user_id = body.user.id; const alert_search_phrase = view.state.values.search_phrase_block.search_phrase_action.value; const alert_message_contents = view.state.values.alert_message_contents_block.alert_message_contents_action.value; const selected_config_options = view.state.values.alert_configuration_block.alert_configuration_action.selected_options;
try { //Retrieve conversation names from conversation IDs const names = await getNames(client, selectedConversations); console.log("Conversation names:", names);
} catch (error) { console.error(error); }
});
// Listen for messages containing "ping" and respond app.message('ping', async ({ message, say }) => { console.log("messge event: specific string"); try { await say({ text:
Hello <@${message.user}>!
, blocks: [ { type: "section", text: { type: "mrkdwn", text:Hey <@${message.user}>, check this out!
}, accessory: { type: "button", text: { type: "plain_text", text: "Click me!" }, url: "https://example.com" } } ] }); } catch (error) { console.error(error); } });(async () => { let port = process.env.PORT; if (port == null || port == "") { port = 8000; } // app.listen(port); await app.start(process.env.PORT || 3000); console.log('Slack app is running on port ' + (process.env.PORT || 3000));
})();