From 2cf163af21d9d60e9396ae1bb3e05aeaee902c32 Mon Sep 17 00:00:00 2001 From: Kale Davis Date: Wed, 2 Sep 2020 23:00:12 -0700 Subject: [PATCH 1/8] Update README.md Fix minor typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71a010eb4..fd94c0ce8 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ const app = new App({ })(); ``` -> ⚙️ By default, Bolt will listen to the `/slack/events` endpoint of your public URL for all incoming requests (whether shortcuts, events, or interactivity payloads). When configuring Request URLs in your app configuration, the should all have `/slack/events` appended by default. You can modify the default behavior using the endpoints option in the App constructor. This option can be set to a string, or an array of strings, of the paths to use instead of '/slack/events'. +> ⚙️ By default, Bolt will listen to the `/slack/events` endpoint of your public URL for all incoming requests (whether shortcuts, events, or interactivity payloads). When configuring Request URLs in your app configuration, they should all have `/slack/events` appended by default. You can modify the default behavior using the endpoints option in the App constructor. This option can be set to a string, or an array of strings, of the paths to use instead of '/slack/events'. ## Listening for events From 8c3df595df75c62e933e086f0011e9b73170f08b Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Fri, 4 Sep 2020 07:22:19 +0900 Subject: [PATCH 2/8] Improve code snippet in README to support Unicode --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd94c0ce8..857db89f7 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Here is an example where the app sends a simple response, so there's no need for ```js // Reverse all messages the app can hear app.message(async ({ message, say }) => { - const reversedText = message.text.split('').reverse().join(''); + const reversedText = [...message.text].reverse().join(""); await say(reversedText); }); ``` From 256ee22218b8330bc95a671b1add08e4f6161bde Mon Sep 17 00:00:00 2001 From: Shane DeWael Date: Fri, 4 Sep 2020 13:23:11 -0700 Subject: [PATCH 3/8] Update client examples and web api --- docs/_basic/ja_listening_events.md | 4 ++-- docs/_basic/ja_opening_modals.md | 5 ++--- docs/_basic/ja_updating_pushing_modals.md | 5 ++--- docs/_basic/listening_events.md | 6 +++--- docs/_basic/listening_responding_shortcuts.md | 8 ++------ docs/_basic/opening_modals.md | 6 +++--- docs/_basic/updating_pushing_modals.md | 6 +++--- docs/_basic/web_api.md | 14 +++++++------- 8 files changed, 24 insertions(+), 30 deletions(-) diff --git a/docs/_basic/ja_listening_events.md b/docs/_basic/ja_listening_events.md index f2f8a7db7..bb63fed45 100644 --- a/docs/_basic/ja_listening_events.md +++ b/docs/_basic/ja_listening_events.md @@ -15,9 +15,9 @@ order: 3 const welcomeChannelId = 'C12345'; // 新しいユーザーがワークスペースに加入したタイミングで、指定のチャンネルにメッセージを送信して自己紹介を促す -app.event('team_join', async ({ event, context }) => { +app.event('team_join', async ({ event, client }) => { try { - const result = await app.client.chat.postMessage({ + const result = await client.chat.postMessage({ token: context.botToken, channel: welcomeChannelId, text: `Welcome to the team, <@${event.user.id}>! 🎉 You can introduce yourself in this channel.` diff --git a/docs/_basic/ja_opening_modals.md b/docs/_basic/ja_opening_modals.md index fe218c50a..810924a80 100644 --- a/docs/_basic/ja_opening_modals.md +++ b/docs/_basic/ja_opening_modals.md @@ -16,13 +16,12 @@ order: 10 ```javascript // コマンド起動をリッスン -app.command('/ticket', async ({ ack, body, context }) => { +app.command('/ticket', async ({ ack, body, client }) => { // コマンドのリクエストを確認 await ack(); try { - const result = await app.client.views.open({ - token: context.botToken, + const result = await client.views.open({ // 適切な trigger_id を受け取ってから 3 秒以内に渡す trigger_id: body.trigger_id, // view の値をペイロードに含む diff --git a/docs/_basic/ja_updating_pushing_modals.md b/docs/_basic/ja_updating_pushing_modals.md index 0db9d0621..4679ebcde 100644 --- a/docs/_basic/ja_updating_pushing_modals.md +++ b/docs/_basic/ja_updating_pushing_modals.md @@ -20,13 +20,12 @@ order: 11 ```javascript // action_id: button_abc のボタンを押すイベントをリッスン // (そのボタンはモーダルの中にあるという想定) -app.action('button_abc', async ({ ack, body, context }) => { +app.action('button_abc', async ({ ack, body, client }) => { // ボタンを押したイベントを確認 await ack(); try { - const result = await app.client.views.update({ - token: context.botToken, + const result = await client.views.update({ // リクエストに含まれる view_id を渡す view_id: body.view.id, // 更新された view の値をペイロードに含む diff --git a/docs/_basic/listening_events.md b/docs/_basic/listening_events.md index 1accc2503..197fa5d83 100644 --- a/docs/_basic/listening_events.md +++ b/docs/_basic/listening_events.md @@ -15,10 +15,10 @@ The `event()` method requires an `eventType` of type string. const welcomeChannelId = 'C12345'; // When a user joins the team, send a message in a predefined channel asking them to introduce themselves -app.event('team_join', async ({ event, context }) => { +app.event('team_join', async ({ event, client }) => { try { - const result = await app.client.chat.postMessage({ - token: context.botToken, + // Call chat.postMessage with the built-in client + const result = await client.chat.postMessage({ channel: welcomeChannelId, text: `Welcome to the team, <@${event.user.id}>! 🎉 You can introduce yourself in this channel.` }); diff --git a/docs/_basic/listening_responding_shortcuts.md b/docs/_basic/listening_responding_shortcuts.md index 01088be32..a9eed8db7 100644 --- a/docs/_basic/listening_responding_shortcuts.md +++ b/docs/_basic/listening_responding_shortcuts.md @@ -23,7 +23,7 @@ When configuring shortcuts within your app configuration, you'll continue to app ```javascript // The open_modal shortcut opens a plain old modal -app.shortcut('open_modal', async ({ shortcut, ack, context, client }) => { +app.shortcut('open_modal', async ({ shortcut, ack, client }) => { try { // Acknowledge shortcut request @@ -31,8 +31,6 @@ app.shortcut('open_modal', async ({ shortcut, ack, context, client }) => { // Call the views.open method using one of the built-in WebClients const result = await client.views.open({ - // The token you used to initialize your app is stored in the `context` object - token: context.botToken, trigger_id: shortcut.trigger_id, view: { type: "modal", @@ -84,15 +82,13 @@ app.shortcut('open_modal', async ({ shortcut, ack, context, client }) => { ```javascript // Your middleware will only be called when the callback_id matches 'open_modal' AND the type matches 'message_action' - app.shortcut({ callback_id: 'open_modal', type: 'message_action' }, async ({ shortcut, ack, context, client }) => { + app.shortcut({ callback_id: 'open_modal', type: 'message_action' }, async ({ shortcut, ack, client }) => { try { // Acknowledge shortcut request await ack(); // Call the views.open method using one of the built-in WebClients const result = await client.views.open({ - // The token you used to initialize your app is stored in the `context` object - token: context.botToken, trigger_id: shortcut.trigger_id, view: { type: "modal", diff --git a/docs/_basic/opening_modals.md b/docs/_basic/opening_modals.md index dd37dad7a..cd284fdb8 100644 --- a/docs/_basic/opening_modals.md +++ b/docs/_basic/opening_modals.md @@ -15,13 +15,13 @@ Read more about modal composition in the { +app.action('button_abc', async ({ ack, body, client }) => { // Acknowledge the button request await ack(); try { - const result = await app.client.views.update({ - token: context.botToken, + // Call views.update with the built-in client + const result = await client.views.update({ // Pass the view_id view_id: body.view.id, // View payload with updated blocks diff --git a/docs/_basic/web_api.md b/docs/_basic/web_api.md index 8c6111c09..4c25fd27f 100644 --- a/docs/_basic/web_api.md +++ b/docs/_basic/web_api.md @@ -6,21 +6,21 @@ order: 4 ---
-You can call [any Web API method](https://api.slack.com/methods) using the [`WebClient`](https://slack.dev/node-slack-sdk/web-api) provided to your Bolt app as `app.client` (given that your app has the appropriate scopes). When you call one the client's methods, it returns a Promise containing the response from Slack. +You can call [any Web API method](https://api.slack.com/methods) using the [`WebClient`](https://slack.dev/node-slack-sdk/web-api) provided to your app's listeners as `client`. This uses either the token that initialized your app or the token that is returned from the [`authorize` function](#authorization) for the incoming event. The built-in [OAuth support](#authenticating-oauth) handles the second case by default. -The token used to initialize Bolt can be found in the `context` object, which is required for most Web API methods. +Your Bolt app also has a top-level `app.client` which you can manually pass the `token` parameter. If the incoming event is not authorized or you're calling a method from outside of a listener, use the top-level `app.client`. + +Calling one of the [`WebClient`](https://slack.dev/node-slack-sdk/web-api)'s methods will return a Promise containing the response from Slack, regardless of whether you use the top-level or listener's client.
```javascript // Unix Epoch time for September 30, 2019 11:59:59 PM const whenSeptemberEnds = 1569887999; -app.message('wake me up', async ({ message, context }) => { +app.message('wake me up', async ({ message, client }) => { try { - // Call the chat.scheduleMessage method with a token - const result = await app.client.chat.scheduleMessage({ - // The token you used to initialize your app is stored in the `context` object - token: context.botToken, + // Call chat.scheduleMessage with the built-in client + const result = await client.chat.scheduleMessage({ channel: message.channel.id, post_at: whenSeptemberEnds, text: 'Summer has come and passed' From 286e3d6e3bc1eb2ba8be331771ff9a5471925f5d Mon Sep 17 00:00:00 2001 From: Shane DeWael Date: Fri, 4 Sep 2020 13:53:02 -0700 Subject: [PATCH 4/8] Add note about OAuth --- docs/_advanced/authorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_advanced/authorization.md b/docs/_advanced/authorization.md index a1ba293e2..5a0210106 100644 --- a/docs/_advanced/authorization.md +++ b/docs/_advanced/authorization.md @@ -8,7 +8,7 @@ order: 2
Authorization is the process of deciding which Slack credentials (such as a bot token) should be available while processing a specific incoming event. -Custom apps installed on a single workspace can simply use the `token` option at the time of `App` initialization. However, when your app needs to handle several tokens, such as cases where it will be installed on multiple workspaces or needs access to more than one user token, the `authorize` option should be used instead. +Custom apps installed on a single workspace can simply use the `token` option at the time of `App` initialization. However, when your app needs to handle several tokens, such as cases where it will be installed on multiple workspaces or needs access to more than one user token, the `authorize` option should be used instead. If you're using the [built-in OAuth support](#authenticating-oauth) authorization is handled by default, so you do not need to pass in an `authorize` option. The `authorize` option can be set to a function that takes an event source as its input, and should return a Promise for an object containing the authorized credentials. The source contains information about who and where the event is coming from by using properties like `teamId` (always available), `userId`, `conversationId`, and `enterpriseId`. From c94a5deb2c8e41580fb7055becee5bdba16d28bb Mon Sep 17 00:00:00 2001 From: Shane DeWael Date: Fri, 4 Sep 2020 13:54:33 -0700 Subject: [PATCH 5/8] forgot to remove something --- docs/_basic/ja_listening_events.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/_basic/ja_listening_events.md b/docs/_basic/ja_listening_events.md index bb63fed45..01f50dc33 100644 --- a/docs/_basic/ja_listening_events.md +++ b/docs/_basic/ja_listening_events.md @@ -18,7 +18,6 @@ const welcomeChannelId = 'C12345'; app.event('team_join', async ({ event, client }) => { try { const result = await client.chat.postMessage({ - token: context.botToken, channel: welcomeChannelId, text: `Welcome to the team, <@${event.user.id}>! 🎉 You can introduce yourself in this channel.` }); From 825b8f5a7ebfa6f40de32778d67185ce79eef756 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Wed, 9 Sep 2020 13:45:47 -0700 Subject: [PATCH 6/8] Add missing section break to Getting Started guide --- docs/_tutorials/getting_started.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/_tutorials/getting_started.md b/docs/_tutorials/getting_started.md index febd64b7e..81ecd793f 100644 --- a/docs/_tutorials/getting_started.md +++ b/docs/_tutorials/getting_started.md @@ -50,6 +50,8 @@ Once you authorize the installation, you'll land on the **OAuth & Permissions** > 💡 Treat your token like a password and [keep it safe](https://api.slack.com/docs/oauth-safety). Your app uses it to post and retrieve information from Slack workspaces. +--- + ### Setting up your local project With the initial configuration handled, it's time to set up a new Bolt project. This is where you'll write the code that handles the logic for your app. From 959f50800be0ae4d87fed154d1db9d7440863398 Mon Sep 17 00:00:00 2001 From: Shay DeWael Date: Wed, 9 Sep 2020 14:04:40 -0700 Subject: [PATCH 7/8] Update and simplify README (#609) * Update README.md Co-authored-by: Kazuhiro Sera * Update README.md Co-authored-by: Kazuhiro Sera * add events api info * add view alias and fix link Co-authored-by: Kazuhiro Sera --- README.md | 356 ++++++++---------------------------------------------- 1 file changed, 50 insertions(+), 306 deletions(-) diff --git a/README.md b/README.md index fd94c0ce8..926422949 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,22 @@ [![Build Status](https://travis-ci.org/slackapi/bolt-js.svg?branch=master)](https://travis-ci.org/slackapi/bolt-js) [![codecov](https://codecov.io/gh/slackapi/bolt/branch/master/graph/badge.svg)](https://codecov.io/gh/slackapi/bolt-js) -A JavaScript framework to build Slack apps in a flash with the latest platform features. +A JavaScript framework to build Slack apps in a flash with the latest platform features. Read the [getting started guide](https://slack.dev/bolt-js/tutorial/getting-started) to set-up and run your first Bolt app. + +Read [the documentation](https://slack.dev/bolt-js) to explore the basic and advanced concepts of Bolt for JavaScript. + +## Setup + +```bash +npm install @slack/bolt + +# Or with yarn +yarn add @slack/bolt +``` ## Initialization -Create an app by calling a constructor, which is a top-level export. +Create an app by calling the constructor, which is a top-level export. ```js const { App } = require('@slack/bolt'); @@ -27,83 +38,56 @@ const app = new App({ })(); ``` -> ⚙️ By default, Bolt will listen to the `/slack/events` endpoint of your public URL for all incoming requests (whether shortcuts, events, or interactivity payloads). When configuring Request URLs in your app configuration, they should all have `/slack/events` appended by default. You can modify the default behavior using the endpoints option in the App constructor. This option can be set to a string, or an array of strings, of the paths to use instead of '/slack/events'. +> ⚙️ By default, Bolt listens to the `/slack/events` endpoint for *all* incoming requests (whether shortcuts, events, or actions payloads). When configuring Request URLs in your app configuration, they should all have `/slack/events` appended. The default behavior be modified using the constructor's `endpoints` parameter. ## Listening for events -Apps typically react to incoming events, which can be events, actions, commands, or options requests. For each type of -event, there's a method to attach a listener function. +Apps typically react to a collection of incoming events, which can correspond [Events API events](https://api.slack.com/events-api), [actions](https://api.slack.com/interactivity/components), [shortcuts](https://api.slack.com/interactivity/shortcuts), [slash commands](https://api.slack.com/interactivity/slash-commands) or [options requests](https://api.slack.com/reference/block-kit/block-elements#external_select). For each type of +request, there's a method to build a listener function. ```js // Listen for an event from the Events API app.event(eventType, fn); -// Listen for an action from a block element (buttons, menus, etc) +// Convenience method to listen to only `message` events using a string or RegExp +app.message([pattern ,] fn); + +// Listen for an action from a Block Kit element (buttons, select menus, date pickers, etc) app.action(actionId, fn); -// Listen for dialog submission, or legacy action +// Listen for dialog submissions app.action({ callback_id: callbackId }, fn); -// Listen for a global shortcut, or message shortcut +// Listen for a global or message shortcuts app.shortcut(callbackId, fn); -// Listen for modal view requests -app.view(callbackId, fn); - -// Listen for a slash command +// Listen for slash commands app.command(commandName, fn); -// Listen for options requests (from menus with an external data source) -app.options(actionId, fn); -``` - -There's a special method that's provided as a convenience to handle Events API events with the type `message`. Also, you -can include a string or RegExp `pattern` before the listener function to only call that listener function when the -message text matches that pattern. +// Listen for view_submission modal events +app.view(callbackId, fn); -```js -app.message([pattern ,] fn); +// Listen for options requests (from select menus with an external data source) +app.options(actionId, fn); ``` ## Making things happen -Most of the app's functionality will be inside listener functions (the `fn` parameters above). These functions are -called with arguments that make it easy to build a rich app. - -* `payload` (aliases: `message`, `event`, `action`, `command`, `options`) - The contents of the event. The - exact structure will depend on which kind of event this listener is attached to. For example, for an event from the - Events API, it will the [event type structure](https://api.slack.com/events-api#event_type_structure) (the portion - inside the event envelope). For a block action or legacy action, it will be the action inside the `actions` array. - The same object will also be available with the specific name for the kind of payload it is. For example, for an - event from a block action, you can use the `payload` and `action` arguments interchangeably. **The easiest way to - understand what's in a payload is to simply log it**, or otherwise [use TypeScript](#using-typescript). +Most of the app's functionality will be inside listener functions (the `fn` parameters above). These functions are called with a set of arguments. -* `say` - A function to respond to an incoming event. This argument is only available when the listener is triggered - for event that contains a `channel_id` (including `message` events). Call this function to send a message back to the - same channel as the incoming event. It accepts both simple strings (for plain messages) and objects (for complex - messages, including blocks or attachments). `say` returns a promise that will resolve with a - [response](https://api.slack.com/methods/chat.postMessage) from `chat.postMessage`. +| Argument | Description | +| :---: | :--- | +| `payload` | Contents of the incoming event. The payload structure depends on the listener. For example, for an Events API event, `payload` will be the [event type structure](https://api.slack.com/events-api#event_type_structure). For a block action, it will be the action from within the `actions` array. The `payload` object is also accessible via the alias corresponding to the listener (`message`, `event`, `action`, `shortcut`, `view`, `command`, or `options`). For example, if you were building a `message()` listener, you could use the `payload` and `message` arguments interchangably. **An easy way to understand what's in a payload is to log it**, or [use TypeScript](https://slack.dev/bolt-js/tutorial/using-typescript). | +| `say` | Function to send a message to the channel associated with the incoming event. This argument is only available when the listener is triggered for events that contain a `channel_id` (the most common being `message` events). `say` accepts simple strings (for plain-text messages) and objects (for messages containing blocks). `say` returns a promise that will resolve with a [`chat.postMessage` response](https://api.slack.com/methods/chat.postMessage). +| `ack` | Function that **must** be called to acknowledge that an incoming event was received by your app. `ack` exists for all actions, shortcuts, view, slash command and options requests. `ack` returns a promise that resolves when complete. Read more in [Acknowledging events](#acknowledging-events) +| `client` | Web API client that uses the token associated with that event. For single-workspace installations, the token is provided to the constructor. For multi-workspace installations, the token is returned by the `authorize` function. +| `respond` | Function that responds to an incoming event **if** it contains a `response_url` (shortcuts, actions, and slash commands). `respond` returns a promise that resolves with the results of responding using the `response_url`. +| `context` | Event context. This object contains data about the event and the app, such as the `botId`. Middleware can add additional context before the event is passed to listeners. +| `body` | Object that contains the entire body of the request (superset of `payload`). Some accessory data is only available outside of the payload (such as `trigger_id` and `authed_users`). -* `ack` - A function to acknowledge that an incoming event was received by the app. Incoming events from actions, - commands, and options requests **must** be acknowledged by calling this function. See [acknowledging - events](#acknowledging-events) for details. `ack` returns a promise that resolves when complete. -* `respond` - A function to respond to an incoming event. This argument is only available when the listener is - triggered for an event that contains a `response_url` (actions and commands). Call this function to send a message - back to the same channel as the incoming event, but using the semantics of the `response_url`. `respond` - returns a promise that resolves with the results of responding using the `response_url`. - -* `context` - The event context. This object contains data about the message and the app, such as the `botId`. - See [advanced usage](#advanced-usage) for more details. - -* `body` - An object that contains the whole body of the event, which is a superset of the data in `payload`. Some - types of data are only available outside the event payload itself, such as `api_app_id`, `authed_users`, etc. This - argument should rarely be needed, but for completeness it is provided here. - -The arguments are grouped into properties of one object, so that it's easier to pick just the ones your listener needs -(using -[object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Unpacking_fields_from_objects_passed_as_function_parameter)). -Here is an example where the app sends a simple response, so there's no need for most of these arguments: +The arguments are grouped into properties of one object, so that it's easier to pick just the ones your listener needs (using +[object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Unpacking_fields_from_objects_passed_as_function_parameter)). Here is an example where the app sends a simple response, so there's no need for most of these arguments: ```js // Reverse all messages the app can hear @@ -115,271 +99,31 @@ app.message(async ({ message, say }) => { ### Calling the Web API -Listeners can use the full power of all the methods in the Web API (given that your app is installed with the -appropriate scopes). Each app has a `client` property that can be used to call methods. Your listener may read the app's -token from the `context` argument, and use it as the `token` argument for a method call. See the [`WebClient` -documentation](https://slack.dev/node-slack-sdk/web-api) for a more complete description of how it can be used. - -```js -// React to any message that contains "happy" with a 😀 -app.message('happy', async ({ message, context }) => { - try { - // Call the "reactions.add" Web API method - const result = await app.client.reactions.add({ - // Use token from context - token: context.botToken, - name: 'grinning', - channel: message.channel, - timestamp: message.ts - }); - console.log(result); - } catch (error) { - console.error(error); - } -}); -``` +In addition to the [`client` property passed to listeners](#making-things-happen), each app has a top-level `client` that can be used to call methods. Unlike the `client` passed to listeners, the top-level client must be passed a `token`. [Read the documentation](https://slack.dev/bolt-js/concepts#web-api) for more details. ### Acknowledging events -Some types of events need to be acknowledged in order to ensure a consistent user experience inside the Slack client -(web, mobile, and desktop apps). This includes all actions, commands, and options requests. Listeners for these events -need to call the `ack()` function, which is passed in as an argument. +Some types of events need to be acknowledged in order to ensure a consistent user experience inside the Slack client (web, mobile, and desktop apps). This includes all action, shortcut, view, command, and options requests. Listeners for these events need to call the `ack()` function, which is passed in as an argument. -In general, the Slack platform expects an acknowledgement within 3 seconds, so listeners should call this function as -soon as possible. +In general, the Slack platform expects an acknowledgement within 3 seconds, so listeners should call this function as soon as possible. Depending on the type of incoming event a listener is meant for, `ack()` should be called with a parameter: * Block actions, global shortcuts, and message shortcuts: Call `ack()` with no parameters. -* Dialog submissions: Call `ack()` with no parameters when the inputs are all valid, or an object describing the - validation errors if any inputs are not valid. +* View submissions: Call `ack()` with no parameters or with a [response action](https://api.slack.com/surfaces/modals/using#updating_response). * Options requests: Call `ack()` with an object containing the options for the user to see. -* Legacy message button clicks, menu selections, and slash commands: Either call `ack()` with no parameters, a `string` - to to update the message with a simple message, or an `object` to replace it with a complex message. Replacing the - message to remove the interactive elements is a best practice for any action that should only be performed once. - -The following is an example of acknowledging a dialog submission: +* Legacy message button clicks, menu selections, and slash commands: Either call `ack()` with no parameters, a `string` to to update the message with a simple message, or an `object` to replace it with a complex message. Replacing the message to remove the interactive elements is a best practice for any action that should only be performed once. -```js -app.action({ callbackId: 'my_dialog_callback' }, async ({ action, ack }) => { - // Expect the ticketId value to begin with "CODE" - if (action.submission.ticketId.indexOf('CODE') !== 0) { - await ack({ - errors: [{ - name: 'ticketId', - error: 'This value must begin with CODE', - }], - }); - return; - } - await ack(); - - // Do some work -}); -``` +* Events API events **do not** need an `ack()` function since they are automatically acknowledged by your app. -## Handling errors - -If an error occurs in a listener function, it's strongly recommended to handle it directly. There are a few cases where -those errors may occur after your listener function has returned (such as when calling `say()` or `respond()`, or -forgetting to call `ack()`). In these cases, your app will be notified about the error in an error handler function. -Your app should register an error handler using the `App#error(fn)` method. - -```js -app.error((error) => { - // Check the details of the error to handle special cases (such as stopping the app or retrying the sending of a message) - console.error(error); -}); -``` +## Getting Help -If you do not attach an error handler, the app will log these errors to the console by default. +[The documentation](https://slack.dev/bolt-js) has more information on basic and advanced concepts for Bolt for JavaScript. -The `app.error()` method should be used as a last resort to catch errors. It is always better to deal with errors in the -listeners where they occur because you can use all the context available in that listener. +If you otherwise get stuck, we're here to help. The following are the best ways to get assistance working through your issue: -## Advanced usage - -Apps are designed to be extensible using a concept called **middleware**. Middleware allow you to define how to process -a whole set of events before (and after) the listener function is called. This makes it easier to deal with common -concerns in one place (e.g. authentication, logging, etc) instead of spreading them out in every listener. - -In fact, middleware can be _chained_ so that any number of middleware functions get a chance to run before the listener, -and they each run in the order they were added to the chain. - -Middleware are just functions - nearly identical to listener functions. They can choose to respond right away, to extend -the `context` argument and continue, or trigger an error. The only difference is that middleware use a special `next` -argument, a function that's called to let the app know it can continue to the next middleware (or listener) in the -chain. - -There are two types of middleware: global and listener. Each are explained below. - -### Global middleware - -Global middleware are used to deal with app-wide concerns, where every incoming event should be processed. They are -added to the chain using `app.use(middleware)`. You can add multiple middleware, and each of them will run in order -before any of the listener middleware, or the listener functions run. - -As an example, let's say your app can only work if the user who sends any incoming message is identified using an -internal authentication service (e.g. an SSO provider, LDAP, etc). Here is how you might define a global middleware to -make that data available to each listener. - -```js -const { App } = require('@slack/bolt'); - -const app = new App({ - signingSecret: process.env.SLACK_SIGNING_SECRET, - token: process.env.SLACK_BOT_TOKEN, -}); - -// Add the authentication global middleware -app.use(authWithAcme); - -// The listener now has access to the user details -app.message('whoami', async ({ say, context }) => { await say(`User Details: ${JSON.stringify(context.user)}`) }); - -(async () => { - // Start the app - await app.start(process.env.PORT || 3000); - console.log('⚡️ Bolt app is running!'); -})(); - -// Authentication middleware - Calls Acme identity provider to associate the incoming event with the user who sent it -// It's a function just like listeners, but it also uses the next argument -async function authWithAcme({ payload, context, say, next }) { - const slackUserId = payload.user; - - try { - // Assume we have a function that can take a Slack user ID as input to find user details from the provider - const user = await acme.lookupBySlackId(slackUserId); - - // When the user lookup is successful, add the user details to the context - context.user = user; - } catch (error) { - if (error.message === 'Not Found') { - // In the real world, you would need to check if the say function was defined, falling back to the respond - // function if not, and then falling back to only logging the error as a last resort. - await say(`I'm sorry <@${slackUserId}>, you aren't registered with Acme. Please use to use this app.`); - return; - } - - // This middleware doesn't know how to handle any other errors. - // Pass control to the previous middleware (if there are any) or the global error handler. - throw error; - } - - // Pass control to the next middleware (if there are any) and the listener functions - // Note: You probably don't want to call this inside a `try` block, or any middleware - // after this one that throws will be caught by it. - await next(); -} -``` - -### Listener middleware - -Listener middleware are used to deal with shared concerns amongst many listeners, but not necessarily for all of them. -They are added as arguments that precede the listener function in the call that attaches the listener function. This -means the methods described in [Listening for events](#listening-for-events) are actually all variadic (they take any -number of parameters). You can add as many listener middleware as you like. - -As an example, let's say your listener only needs to deal with messages from humans. Messages from apps will always have -a subtype of `bot_message`. We can write a middleware that excludes bot messages, and use it as a listener middleware -before the listener attached to `message` events: - -```js -// Listener middleware - filters out messages that have subtype 'bot_message' -async function noBotMessages({ message, next }) { - if (!message.subtype || message.subtype !== 'bot_message') { - await next(); - } -} - -// The listener only sees messages from human users -app.message(noBotMessages, ({ message }) => console.log( - `(MSG) User: ${message.user} - Message: ${message.text}` -)); -``` - -Message subtype matching is common, so Bolt for JavaScript ships with a builtin listener middleware that filters all messages that -match a given subtype. The following is an example of the opposite of the one above - the listener only sees messages -that _are_ `bot_message`s. - -```js -const { App, subtype } = require('@slack/bolt'); - -// Not shown: app initialization and start - -// The listener only sees messages from bot users (apps) -app.message(subtype('bot_message'), ({ message }) => console.log( - `(MSG) Bot: ${message.bot_id} - Message: ${message.text}` -)); -``` - -### Even more advanced usage - -The examples above all illustrate how middleware can be used to process an event _before_ the listener (and other -middleware in the chain) run. However, middleware can be designed to process the event _after_ the listener finishes. -In general, a middleware can run both before and after the remaining middleware chain. - -How you use `next` can -have four different effects: - -* **To both preprocess and post-process events** - You can choose to do work both _before_ listener functions by putting code - before `await next()` and _after_ by putting code after `await next()`. `await next()` passes control down the middleware - stack in the order it was defined, then back up it in reverse order. - -* **To throw an error** - If you don't want to handle an error in a listener, or want to let an upstream listener - handle it, you can simply **not** call `await next()` and `throw` an `Error`. - -* **To handle mid-processing errors** - While not commonly used, as `App#error` is essentially a global version of this, - you can catch any error of any downstream middleware by surrounding `await next()` in a `try-catch` block. - -* **To break the middleware chain** - You can stop middleware from progressing by simply not calling `await next()`. - By it's nature, throwing an error tends to be an example of this as code after a throw isn't executed. - -The following example shows a global middleware that calculates the total processing time for the middleware chain by -calculating the time difference from before the listener and after the listener: - -```js -async function logProcessingTime({ next }) { - const startTimeMs = Date.now(); - - await next(); - - const endTimeMs = Date.now(); - console.log(`Total processing time: ${endTimeMs - startTimeMs}`); -} - -app.use(logProcessingTime) -``` - -The next example shows a series of global middleware where one generates an error -and the other handles it. - -```js -app.use(async ({ next, say }) => { - try { - await next(); - } catch (error) { - if (error.message === 'channel_not_found') { - // Handle known errors - await say('It appears we can't access that channel') - } else { - // Rethrow for an upstream error handler - throw error; - } - } -}) - -app.use(async () => { - throw new Error('channel_not_found') -}) - -app.use(async () => { - // This never gets called as the middleware above never calls next -}) -``` + * [Issue Tracker](http://github.com/slackapi/bolt-js/issues) for questions, bug reports, feature requests, and general discussion related to Bolt for JavaScript. Try searching for an existing issue before creating a new one. + * [Email](mailto:support@slack.com) our developer support team: `support@slack.com` From 8845d7ff95a21314bc460a4dcb5e969ec2e8d939 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Thu, 10 Sep 2020 08:09:41 +0900 Subject: [PATCH 8/8] Apply #617 changes to Japanese version --- docs/_tutorials/ja_getting_started.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/_tutorials/ja_getting_started.md b/docs/_tutorials/ja_getting_started.md index c17eb5eec..da0e1b0a2 100644 --- a/docs/_tutorials/ja_getting_started.md +++ b/docs/_tutorials/ja_getting_started.md @@ -51,6 +51,8 @@ Slack アプリに使用できるトークンには、user(`xoxp`) トークン > 💡 トークンは、パスワードのように大切に扱い、[安全に保管](https://api.slack.com/docs/oauth-safety)してください。アプリではそのトークンを使用して、Slack ワークスペースからの情報を投稿および取得します。 +--- + ### ローカルプロジェクトの設定 初期設定が完了したので、次は新しい Bolt プロジェクトを設定します。ここで、アプリのロジックを処理するコードを記述します。