Skip to content

Commit

Permalink
Merge branch 'master' into ref-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengill authored Nov 26, 2019
2 parents c956107 + f96e332 commit 9c3260e
Show file tree
Hide file tree
Showing 36 changed files with 1,428 additions and 174 deletions.
7 changes: 1 addition & 6 deletions .github/maintainers_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@ Test code should be written in syntax that runs on the oldest supported Node.js
ensures that backwards compatibility is tested and the APIs look reasonable in versions of Node.js that do not support
the most modern syntax.

**TODO** update the following information for debugging with VSCode or with the protocol directly.

A useful trick for debugging inside tests is to use the Chrome Debugging Protocol feature of Node.js to set breakpoints
and interactively debug. In order to do this you must run mocha directly. This means that you should have already linted
the source (`npm run lint`) manually. You then run the tests using the following command: `./node_modules/.bin/mocha
test/{test-name}.js --debug-brk --inspect` (replace {test-name} with an actual test file).
We have included `launch.json` files that store configuration for `vscode` debugging in each pacakge. This allows you to set breakpoints in test files and interactively debug. Open the project in `vscode` and navigate to the debug screen on the left sidebar. The icon for it looks like a little lock bug with an x inside. At the top in `vscode`, select the configuration to run and press the green play icon to start debugging. Alternatively, on mac, you can press `cmd + shift + d` to get to the debug screen and `F5` to start debugging. If you are using `vscode` debugging, don't forget to lint the source (`npm run lint`) manually.

### Generating Documentation

Expand Down
2 changes: 1 addition & 1 deletion docs/_main/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ look next:

* Dive into the [`@slack/events-api`](https://slack.dev/node-slack-sdk/events_api) package to learn how your app can
listen for events happening inside Slack. You'll need a URL where your app can receive events, and the [local
development tutorial](https://slack.dev/node-slack-sdk/local_development) can help you set one up.
development tutorial](https://slack.dev/node-slack-sdk/tutorials/local-development) can help you set one up.

* This tutorial only used two of **over 130 Web API methods** available. [Look through
them](https://api.slack.com/methods) to get ideas about what to build next!
Expand Down
129 changes: 121 additions & 8 deletions docs/_packages/interactive_messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,117 @@ slackInteractions.options({ within: 'dialog' }, (payload) => {

---

### Handling view submission and view closed interactions

View submissions are generated when a user clicks on the submission button of a
[Modal](https://api.slack.com/surfaces/modals). View closed interactions are generated when a user clicks on the cancel
button of a Modal, or dismisses the modal using the `×` in the corner.

Apps register functions, called **handlers**, to be triggered when a submissions are received by the adapter using the
`.viewSubmission(constraints, handler)` method or when closed interactions are received using the
`.viewClosed(constraints, handler)` method. When registering a handler, you describe which submissions and closed
interactions 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 interaction best.

These handlers receive a single `payload` argument. The `payload` describes the
[view submission](https://api.slack.com/reference/interaction-payloads/views#view_submission) or
[view closed](https://api.slack.com/reference/interaction-payloads/views#view_closed)
interaction that occurred.

For view submissions, 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 what you'd like to happen to the view.
Your app can update the view, push a new view into the stack, close the view, or display validation errors to the user.
In the documentation, the shape of the objects for each of those possible outcomes, which the handler would return, are
described as `response_action`s. If the handler returns no value, or a Promise that resolves to no value, the view will
simply be dismissed on submission.

View closed interactions only occur if the view was opened with the `notify_on_close` property set to `true`. For these
interactions the handler should not return a value.

```javascript
const { createMessageAdapter } = require('@slack/interactive-messages');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackInteractions = createMessageAdapter(slackSigningSecret);
const port = process.env.PORT || 3000;

// Example of handling a simple view submission
slackInteractions.viewSubmission('simple_modal_callback_id', (payload) => {
// Log the input elements from the view submission.
console.log(payload.view.state);

// The previous value is an object keyed by block_id, which contains objects keyed by action_id,
// which contains value properties that contain the input data. Let's log one specific value.
console.log(payload.view.state.my_block_id.my_action_id.value);

// Validate the inputs (errors is of the shape in https://api.slack.com/surfaces/modals/using#displaying_errors)
const errors = validate(payload.view.state);

// Return validation errors if there were errors in the inputs
if (errors) {
return errors;
}

// Process the submission
doWork();
});

// Example of handling a view submission which pushes another view onto the stack
slackInteractions.viewSubmission('first_step_callback_id', () => {
const errors = validate(payload.view.state);

if (errors) {
return errors;
}

// Process the submission (needs to complete under 2.5 seconds)
return doWork()
.then(() => {
return {
response_action: 'push',
view: {
type: 'modal',
callback_id: 'second_step_callback_id',
title: {
type: 'plain_text',
text: 'Second step',
},
blocks: [
{
type: 'input',
block_id: 'last_thing',
element: {
type: 'plain_text_input',
action_id: 'text',
},
label: {
type: 'plain_text',
text: 'One last thing...',
},
},
],
},
};
})
.catch((error) => {
// Log the error. In your app, inform the user of a failure using a DM or some other area in Slack.
console.log(error);
});
});

// Example of handling view closed
slackInteractions.viewClosed('my_modal_callback_id', (payload) => {
// If you accumulated partial state using block actions, now is a good time to clear it
clearPartialState();
});

(async () => {
const server = await slackInteractions.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
```

---

### Constraints

Constraints allow you to describe when a handler should be called. In simpler apps, you can use very simple constraints
Expand All @@ -356,14 +467,16 @@ conditions, and express a more nuanced structure of your app.

Constraints can be a simple string, a `RegExp`, or an object with a number of properties.

| 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 || 🚫 |
| Property name | Type | Description | Used with `.action()` | Used with `.options()` | Used with `.viewSubmission()` and `.viewClosed()` |
|---------------|------|-------------|-----------------------|------------------------|---------------------------------------------------|
| `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 || 🚫 | 🚫 |
| `viewId` | `string` | Match the `view_id` for view submissions | 🚫 | 🚫 ||
| `externalId` | `string` or `RegExp` | Match the `external_id` for view submissions | 🚫 | 🚫 ||

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:
Expand Down
91 changes: 91 additions & 0 deletions docs/_packages/web_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,97 @@ pattern for people that use _functional programming_.

---

### Opening modals
[Modals](https://api.slack.com/block-kit/surfaces/modals) can be created by calling the `views.open` method. The method requires you to pass a valid [view payload](https://api.slack.com/reference/block-kit/views) in addition to a `trigger_id`, which can be obtained when a user invokes your app using a slash command, clicking a button, or using [another interactive action](https://api.slack.com/reference/messaging/interactive-components).

```javascript
const { WebClient } = require('@slack/web-api');

// trigger_ids can be obtained when a user invokes your app.
// Find more information on triggers: https://api.slack.com/docs/triggers
const trigger = 'VALID_TRIGGER_ID';

(async () => {

// Open a modal.
// Find more arguments and details of the response: https://api.slack.com/methods/views.open
const result = await web.views.open({
trigger_id: trigger,
view: {
type: 'modal',
callback_id: 'view_identifier',
title: {
type: 'plain_text',
text: 'Modal title'
},
submit: {
type: 'plain_text',
text: 'Submit'
},
blocks: [
{
type: 'input',
label: {
type: 'plain_text',
text: 'Input label'
},
element: {
type: 'plain_text_input',
action_id: 'value_indentifier'
}
}
]
}
});

// The result contains an identifier for the root view, view.id
console.log(`Successfully opened root view ${result.view.id}`);
})();
```

<details>
<summary markdown="span">
<strong><i>Dynamically updating a modal</i></strong>
</summary>

After the modal is opened, you can update it dynamically by calling `views.update` with the view ID returned in the `views.open` result.

```javascript
const { WebClient } = require('@slack/web-api');

// The view ID returned in a views.open call
const vid = 'YOUR_VIEW_ID';

(async () => {

// Update a modal
// Find more arguments and details of the response: https://api.slack.com/methods/views.update
const result = await web.views.update({
view_id: vid
view: {
type: 'modal',
callback_id: 'view_identifier',
title: {
type: 'plain_text',
text: 'Modal title'
},
blocks: [
{
type: 'section',
text: {
type: 'plain_text',
text: 'An updated modal, indeed'
}
}
]
}
});
})();
```
</details>

---

### Logging

The `WebClient` will log interesting information to the console by default. You can use the `logLevel` to decide how
Expand Down
39 changes: 39 additions & 0 deletions integration-tests/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Integration Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/proxy-test.js"
],
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": [
"<node_internals>/**"
],
"cwd": "${workspaceFolder}"
},
{
"type": "node",
"request": "launch",
"name": "Integration Type Checker",
"program": "${workspaceFolder}/node_modules/dtslint/bin/index.js",
"args": [
"types"
],
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": [
"<node_internals>/**"
],
"cwd": "${workspaceFolder}"
},
]
}
10 changes: 5 additions & 5 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@
"test": "npm run build && echo \"Tests are not implemented.\" && exit 0"
},
"dependencies": {
"@slack/logger": "^1.0.0",
"@slack/rtm-api": "^5.0.2",
"@slack/types": "^1.1.0",
"@slack/web-api": "^5.1.0",
"@slack/webhook": "^5.0.1"
"@slack/logger": ">=1.0.0 <3.0.0",
"@slack/rtm-api": "^5.0.3",
"@slack/types": "^1.2.1",
"@slack/web-api": "^5.3.0",
"@slack/webhook": "^5.0.2"
},
"devDependencies": {
"shx": "^0.3.2",
Expand Down
26 changes: 26 additions & 0 deletions packages/events-api/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Events API Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/test/integration/*.js",
"${workspaceFolder}/src/*.spec.js"
],
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": [
"<node_internals>/**"
],
"cwd": "${workspaceFolder}"
},
]
}
11 changes: 0 additions & 11 deletions packages/events-api/src/http-handler.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,6 @@ describe('http-handler', function () {
this.requestListener(req, res);
});

it('should fail request signing verification when a request has body and no rawBody attribute', function (done) {
const res = this.res;
const req = createRequest('INVALID_SECRET', this.correctDate, correctRawBody);
getRawBodyStub.resolves(Buffer.from(correctRawBody));
res.end.callsFake(function () {
assert.equal(res.statusCode, 404);
done();
});
this.requestListener(req, res);
});

it('should fail request signing verification with old timestamp', function (done) {
const res = this.res;
const sixMinutesAgo = Math.floor(Date.now() / 1000) - (60 * 6);
Expand Down
6 changes: 6 additions & 0 deletions packages/events-api/src/http-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ enum ResponseStatus {
Failure = 500,
}

/**
* A RequestListener-compatible callback for creating response information from an incoming request.
*
* @remarks
* See RequestListener in the `http` module.
*/
type HTTPHandler = (req: IncomingMessage & { body?: any, rawBody?: Buffer }, res: ServerResponse) => void;

/**
Expand Down
Loading

0 comments on commit 9c3260e

Please sign in to comment.