Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Bolt for JavaScript (TypeScript) Template App
# Work Objects Sample App

This is a generic Bolt for JavaScript (TypeScript) template app used to build out Slack apps.
This sample app generates Work Object unfurls when a Miro Board URL is posted in a Slack channel. Additionally, it provides data for the Work Objects flexpane when a user clicks an unfurl.

Before getting started, make sure you have a development workspace where you have permissions to install apps. If you don’t have one setup, go ahead and [create one](https://slack.com/create).
To learn more about implementing Work Objects in your Slack app, visit the [Work Object documentation page]().

## Installation

Before getting started, make sure you have a development workspace where you have permissions to install apps. If you don’t have one setup, go ahead and [create one](https://slack.com/create).

#### Create a Slack App

1. Open [https://api.slack.com/apps/new](https://api.slack.com/apps/new) and choose "From an app manifest"
Expand All @@ -20,13 +22,20 @@ Before you can run the app, you'll need to store some environment variables.

1. Copy `env.sample` to `.env`
2. Open your apps configuration page from [this list](https://api.slack.com/apps), click _OAuth & Permissions_ in the left hand menu, then copy the _Bot User OAuth Token_ into your `.env` file under `SLACK_BOT_TOKEN`
3. Click _Basic Information_ from the left hand menu and follow the steps in the _App-Level Tokens_ section to create an app-level token with the `connections:write` scope. Copy that token into your `.env` as `SLACK_APP_TOKEN`.

Steps to setup Miro
1. Create a Miro app by following the steps listed [here](https://developers.miro.com/docs/task-3-run-your-first-app-in-miro)
2. Install the app on Miro and copy the OAuth token that you receive to the `MIRO_TOKEN` variable in `.env`

#### Install Dependencies

`npm install`

#### Run Bolt Server
#### Build Project

`npm run-script build`

#### Run Project

`npm start`

Expand All @@ -36,19 +45,16 @@ Before you can run the app, you'll need to store some environment variables.

`manifest.json` is a configuration for Slack apps. With a manifest, you can create an app with a pre-defined configuration, or adjust the configuration of an existing app.

### `app.ts`
### `server.ts`

`app.ts` is the entry point for the application and is the file you'll run to start the server. This project aims to keep this file as thin as possible, primarily using it as a way to route inbound requests.
`server.ts` is the entry point for the application and the setup of the Slack Client.

### `/listeners`
### `/events`

Every incoming request is routed to a "listener". Inside this directory, we group each listener based on the Slack Platform feature used, so `/listeners/shortcuts` handles incoming [Shortcuts](https://api.slack.com/interactivity/shortcuts) requests, `/listeners/views` handles [View submissions](https://api.slack.com/reference/interaction-payloads/views#view_submission) and so on.
Every incoming request is sent to `event-handler.ts`, which routes every incoming request to the appropriate event handler.

## App Distribution / OAuth

Only implement OAuth if you plan to distribute your application across multiple workspaces. A separate `app-oauth.ts` file can be found with relevant OAuth settings.

When using OAuth, Slack requires a public URL where it can send requests. In this template app, we've used [`ngrok`](https://ngrok.com/download). Checkout [this guide](https://ngrok.com/docs#getting-started-expose) for setting it up.
Slack requires a public URL where it can send requests. In this template app, we've used [`ngrok`](https://ngrok.com/download). Checkout [this guide](https://ngrok.com/docs#getting-started-expose) for setting it up.

Start `ngrok` to access the app on an external network and create a redirect URL for OAuth.

Expand Down
74 changes: 0 additions & 74 deletions app-oauth.ts

This file was deleted.

26 changes: 0 additions & 26 deletions app.ts

This file was deleted.

7 changes: 2 additions & 5 deletions env.sample
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
SLACK_CLIENT_ID=YOUR_SLACK_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_SLACK_CLIENT_SECRET
SLACK_SIGNING_SECRET=YOUR_SLACK_SIGNING_SECRET
SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKEN
SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN
SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN
MIRO_TOKEN=YOUR_MIRO_TOKEN
69 changes: 69 additions & 0 deletions events/entity-details-requested.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import axios from 'axios';
import { get_miro_board } from '../services/miro'
import { convert_datetime_to_timestamp } from '../utils/time'

export const entity_details_requested = async (type, event, res, slackClient,) => {
try {
const miroBoard = await get_miro_board(event.object_id);

const metadata = {
entity_type: 'slack#/entities/file',
entity_payload: {
attributes: {
url: event.link,
unique_identifier: event.object_id,
title: {
text: miroBoard.name,
},
display_type: `Miro ${miroBoard.type}`,
product_name: "Miro"
},
fields: {
created_by: {
value: miroBoard.createdBy.name,
type: 'string'
},
preview: {
alt_text: 'Miro Board image',
image_url: miroBoard.picture.imageUrl
},
last_modified_by: {
value: miroBoard.modifiedBy.name,
type: 'string'
},
date_created: {
value: convert_datetime_to_timestamp(miroBoard.createdAt),
},
date_updated: {
value: convert_datetime_to_timestamp(miroBoard.modifiedAt),
},
file_size: {
value: "NA"
},
mime_type: {
value: "Miro"
}
},
display_order: ["created_by", "last_modified_by", "date_created", "date_updated", "file_size", "mime_type", "preview"]
}
};

await axios.post(
'https://slack.com/api/entity.presentDetails',
{
user: event.user,
source_id: event.object_id,
user_auth_required: false,
metadata: metadata
},
{
headers: {
'Authorization': `Bearer ${process.env.SLACK_BOT_TOKEN}`,
'Content-Type': 'application/json',
},
}
);
} catch (error) {
console.error(error);
}
};
15 changes: 15 additions & 0 deletions events/event-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// handles link unfurling events sent from Slack to the app
import { link_shared } from "./link-shared";
import { entity_details_requested } from "./entity-details-requested"

export const event_handler = async (type, event, res, slackClient) => {
res.status(200).send("Event received");

if (event.type === "link_shared") {
link_shared(type, event, res, slackClient);
} else if (event.type === "entity_details_requested") {
entity_details_requested(type, event, res, slackClient);
} else {
console.log(`Event not supported: ${event.type}`)
}
};
60 changes: 60 additions & 0 deletions events/link-shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { get_miro_board } from '../services/miro'
import { extract_miro_board_id } from '../utils/miro'

export const link_shared = async (type, event, res, slackClient) => {
try {
const link = event.links[0]; // only unfurl the first link shared
const boardId = extract_miro_board_id(link.url);

if (boardId) {
const miroBoard = await get_miro_board(boardId);

// create a Work Object unfurl for a File entity type
const metadata = {
entities: [
{
app_unfurl_url: link.url,
entity_type: 'slack#/entities/file',
entity_payload: {
attributes: {
url: link.url,
unique_identifier: boardId,
title: {
text: miroBoard.name,
},
display_type: `Miro ${miroBoard.type}`,
product_name: "Miro"
},
fields: {
preview: {
alt_text: 'Miro Board image',
image_url: miroBoard.picture.imageUrl
},
},
custom_fields: [
{
key: "starred",
label: "Starred",
value: "Yes",
type: "string"
}
],
display_order: ["starred", "preview"]
},
}
]
};

await slackClient.chat.unfurl({
channel: event.channel,
ts: event.message_ts,
unfurls: {}, // send your existing unfurl here
metadata: metadata
});
} else {
console.log(`No board ID detected. Do not unfurl. URL: ${link.url}`)
}
} catch (error) {
console.error(error);
}
};
5 changes: 5 additions & 0 deletions events/url-verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Slack will verify the Events URL set for a Slack app
export const url_verify = async (type, challenge, event, res) => {
res.json({ challenge });
return;
};
8 changes: 0 additions & 8 deletions listeners/actions/index.ts

This file was deleted.

Loading
Loading