Closed rmrbytes closed 2 years ago
Any Progress on This issue?
I would also be interested as a possible way to solve https://github.com/slackapi/bolt/issues/283
is this related to expressReceiver
?
Is there any update on this?
I'm trying to implement it in my NestJS application but neither NestJS or Bolt allows me to pass an existing express instance.
Same boat as @regniblod - trying to combine Nestjs and bolt. Any update on this?
Hi folks! Integrating Bolt for JS into other HTTP servers/frameworks is something we're interested in making happen. Within the team, we've called this idea "upward modularity" since its about making Bolt fit inside a larger app. (It's probably not important but "downward modularity" would be about combining several parts of a Bolt app into one Bolt app).
We want this to work in a generic way, so that Bolt can integrate not only into NestJS, but into nearly any web server/framework (Express, hapi, plain Node http
servers, Koa, etc). In fact, there's some prior work to integrate Bolt into a Koa application by @barlock's team here.
The way to move this topic forward would be with a proposal. If you have a specific idea for how you think this should work, please go ahead and write up a description. It doesn't need to be anything formal, just something to describe how you'd like to see the feature work. We can help suss out any questions that arise from that, and the community can help design a solution.
PS. If you just want to hook into Bolt's underlying express app by adding a few custom routes, you can already do that, but we need to document that better.
Meanwhile, this worked for me -
Extract the express app from bolt and add nestjs middleware.
import { App, ExpressReceiver } from '@slack/bolt';
import { AppMiddleware } from './nestj/app.middleware';
const receiver = new ExpressReceiver({ signingSecret: configuration.slackSigningSecret });
const app = new App({
receiver,
token: configuration.slackAccessToken,
signingSecret: configuration.slackSigningSecret,
});
receiver.app.use((req, res, next) => {
const nest = new AppMiddleware(app).use(req, res, next);
nest.then(() => {
next();
}).catch(err => {
next();
});
});
// Start your app
const port = configuration.port || 3000;
await app.start(port);
console.log("⚡️ Bolt app is running! on port " + port);
https://slack.dev/bolt-js/concepts#custom-routes
I followed this SO answer to implement the NestJS middleware.
app.middelware.ts
import { Injectable, NestMiddleware } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { ExpressAdapter } from '@nestjs/platform-express'; import { AppModule } from './app.module';
const bootstrap = async (express: Express.Application) => { const app = await NestFactory.create(AppModule, new ExpressAdapter(express)); await app.init(); return app; }
@Injectable() export class AppMiddleware implements NestMiddleware {
constructor(private expressInstance: Express.Application) {}
use(req: any, res: any, next: () => void) { console.log('In Nest middleware'); return bootstrap(this.expressInstance); } }
Hi! I think it would be great if we could do something like:
const { App } = require('@slack/bolt');
const express = require('express')
const expressApp = express()
const boltApp = new App({
token: configuration.slackAccessToken,
signingSecret: configuration.slackSigningSecret,
});
const boltMiddleware = boltApp.getMiddleware();
expressApp.use('/bolt', boltMiddleware);
Works fine for me:
const { App } = require('@slack/bolt');
const express = require('express');
const app = express();
const boltApp = new App({
signingSecret: config.slackApp.signingSecret,
token: config.slackApp.token,
endpoints = '/'
});
app.use('/slack/events', boltApp.receiver.router); // works also with boltApp.receiver.app
You can add a path in the app.use
and modify the Bolt endpoint(s).
To cover the Hapi framework v.17+ you can jerry-rig a solution by registering Bolt's provided Express Receiver as a plugin using the hecks package. It's not the most elegant solution but I needed something that worked quickly... Hope it helps someone!
const Hapi = require('@hapi/hapi');
const Hecks = require('hecks');
const { App, ExpressReceiver } = require('@slack/bolt');
// INIT BOLT APP
const Receiver = new ExpressReceiver({ signingSecret: SLACK_SIGNING_SECRET });
const BoltApp = new App({
token: SLACK_BOT_TOKEN,
signingSecret: SLACK_SIGNING_SECRET,
receiver: Receiver
});
// INIT HAPI SERVER
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
await server.register([Hecks.toPlugin(BoltApp.receiver.app, 'my-bolt-app')]);
server.route({
method: '*',
path: '/slack/events',
handler: {
express: BoltApp.receiver.app
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
Hi there!! Thank you for all of your advices.
My scenario is to combine multiple slack apps in a same server.
In TS + express, like this:
createApp.ts
import { App, ExpressReceiver } from '@slack/bolt';
export const createApp = (appName: string) => {
const signingSecret = ...;
const token = ...;
const receiver = new ExpressReceiver({
signingSecret,
endpoints: {
events: `/${appName}/slack/events`,
},
});
const app = new App({
token,
receiver,
});
return { app, receiver };
};
one slack app: app-a.ts
import { createApp } from './createApp';
const { app, receiver } = createApp('app-a');
app.message('hello app-a', async ({ body, say }) => {
await say(`Hey there, I'm app-a`);
});
export { receiver };
server.ts
import express from 'express';
import { receiver as appA } from './app-a';
import { receiver as appB } from './app-b';
const app = express();
// you can add more apps
app.use(appA.router); // App A's Event Subscription > Request URL is https://yourserver/app-a/slack/events
app.use(appB.router); // App B's Event Subscription > Request URL is https://yourserver/app-b/slack/events
app.listen(3030);
receiver
Thanks @tell-y works like a charm 🙏
hi there!
context: @slack/bolt 3.4.0 + typescript
with a lot of inspiration from this thread and @tell-y code, I still can't get slack to connect to my public server; the ultimate objective is to get slashCommands working with bolt as it did using a custom solution.
maybe anyone of you see something obvious that is missing or mis-configured... ?
status:
https://xxx/hooks/slack/events
returns correctly a 401 unauthorized
, as no bot/secret is passed, this confirms that the route is correctly setup (Logs indicate missing signing secret)Your URL didn't respond with the value of the challenge parameter.
/yep
), Permissions (chat:write commands file:write
), Bot (no Incoming, no Interactive, no EventsSubscription as it does not work){"level":50,"time":1624016171623,"pid":21,"hostname":"a1d27051-98ca-46f8-a4b7-34cf0402a708","message":"request aborted","code":"ECONNABORTED","expected":null,"length":null,"received":0,"type":"request.aborted","stack":"BadRequestError: request aborted\n at IncomingMessage.onAborted (/app/node_modules/raw-body/index.js:231:10)\n at IncomingMessage.emit (events.js:210:5)\n at IncomingMessage.EventEmitter.emit (domain.js:475:20)\n at abortIncoming (_http_server.js:492:9)\n at socketOnEnd (_http_server.js:508:5)\n at Socket.emit (events.js:215:7)\n at Socket.EventEmitter.emit (domain.js:475:20)\n at endReadableNT (_stream_readable.js:1184:12)\n at processTicksAndRejections (internal/process/task_queues.js:80:21)","msg":[]}
main code bits:
file src/connectors/slack.ts
:
import { ExpressReceiver, App, LogLevel } from '@slack/bolt';
const receiver = new ExpressReceiver({
signingSecret: config.SLACK_SIGNING_SECRET,
endpoints: '/',
});
export const app = new App({
token: config.SLACK_BOT_TOKEN,
receiver,
socketMode: false,
logLevel: LogLevel.DEBUG,
});
export const router = receiver.router;
file server.ts
:
import * as slack from '@connectors/slack';
...
const app = express();
...
app.use('/hooks/slack/events', slack.router);
...
slack.app.message('hello', async ({ message, say }) => {
await say(`Hey <@${message.channel}>!`);
});
slack.app.command('/yep', async ({ command, ack, say }) => {
await ack();
await say(`>>2> ${command.text}`);
});
...
@bertho-zero @cShingleton @aoberoi When I try the approach with boltApp.receiver.router
or boltApp.receiver.app
I'm getting:
Property 'receiver' is private and only accessible within class 'App'
Does anyone have a working example of integrating Bolt into an existing express app? I'm struggling to find a clear migration example from the deprecated slack packages.
@captDaylight I had the same issue; the solution is in above code receiver = new Receiver();
+ new App({..., receiver})
then use receiver.router
Thanks @jyb247, when I consolidate your comment with another one from above I get something like this:
import { App, ExpressReceiver } from '@slack/bolt';
import express from 'express';
const app = express();
const receiver = new ExpressReceiver({ signingSecret: process.env.SLACK_SIGNING_SECRET });
const boltApp = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
receiver,
});
app.use('/', receiver.router);
EDIT: Removed error message, resolved issue
@captDaylight how does your package.json look like? (list only lines with slack
)
@jyb247 disregard, thanks for the help!
@captDaylight so I guess all is working on your side, incl events and slashCommands ? if so, could you share your Slack App Configuration (as to find a solution to my original solution)
@jyb247 I just have the basics done, but I'll post an update early next week once I've made some more progress.
Hey guys, we have attempted to integrate Bolt with Express. Since the slack team works at bolt level, we thought of offering a higher-level abstraction, leveraging Express with simple structure and brought in Bolt. The idea is to build a starter kit for building Slack (and Teams app) apps without worrying about low-level details.
This is in the draft stage, ideas & suggestions are welcome!
@jyb247 did you ever get a solution for the UnhandledPromiseRejectionWarning: BadRequestError: request aborted
issue? I think I'm getting exactly the same problem as you are:
App listening on port 3000.
(node:187857) UnhandledPromiseRejectionWarning: BadRequestError: request aborted
at IncomingMessage.onAborted (/home/alex/dev/personal/membr-bot/node_modules/raw-body/index.js:231:10)
at IncomingMessage.emit (events.js:315:20)
at abortIncoming (_http_server.js:561:9)
at socketOnEnd (_http_server.js:577:5)
at Socket.emit (events.js:327:22)
at endReadableNT (internal/streams/readable.js:1327:12)
at processTicksAndRejections (internal/process/task_queues.js:80:21)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:187857) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:187857) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
@alexbaileyuk @jyb247
I came across the same issues as you mentioned (request aborted). Make sure when you hook the router to your express app, you do it before all the other middlewares that manipulate the body (ie. before json parsers, body parsers, etc).
The ExpressReceiver
has its own built in body parser
@jyb247 I just have the basics done, but I'll post an update early next week once I've made some more progress.
Did you figure it out with events and commands?
@8YOne that solved it for me thanks!
Is there a simple working example for this scenario?
I suspect this will resolve this issue ? https://github.com/slackapi/bolt-js/issues/868
I'm currently working on a rewrite from @slack/interactive-messages
. I have an express app handling multiple bots at subpaths. Just coming here to thank you guys, because your suggestions worked. Here is a simple example of what works for me:
const app = express()
const boltReceiver = new ExpressReceiver({signingSecret, endpoints: '/'})
const boltApp = new App({token: botToken, receiver: boltReceiver})
boltApp.event('member_joined_channel', ({event}) => handleMemberJoined(event))
boltApp.event('message', ({event}) => handleMessage(event))
app.use(`/events/${botSubpath}`, boltReceiver.router)
Simple as that. Thanks again.
Works fine for me:
const { App } = require('@slack/bolt'); const express = require('express'); const app = express(); const boltApp = new App({ signingSecret: config.slackApp.signingSecret, token: config.slackApp.token, endpoints = '/' }); app.use('/slack/events', boltApp.receiver.router); // works also with boltApp.receiver.app
You can add a path in the
app.use
and modify the Bolt endpoint(s).
I'm currently doing something like this except in typescript, it complains that receiver
is private.
@billyvg Hi, it was mentioned later in the thread that this is indeed an issue. Please check my last post for a clean solution in typescript.
@rtrembecky Thank you for coming back and updating your code examples!
I am trying to implement this, but I keep getting a type error on the boltReceiver.router passed to app.use as the second param.
No overload matches this call.
The last overload gave the following error.
Argument of type 'IRouter' is not assignable to parameter of type 'Application'.
Type 'IRouter' is missing the following properties from type 'Application': init, defaultConfiguration, engine, set, and 30 more.ts(2769)
index.d.ts(48, 5): The last overload is declared here.
As far as I can tell, what I have is functionally the same as your code snippet and I'm left scratching my head.
Have you noticed/had to work around this type error at all? I'm using "@slack/bolt": "3.8.1",
and "express": "^4.17.1"
In case that sticks out to anyone.
@zaclittleberry Hi, I have "@slack/bolt": "^3.8.1",
and "express": "^4.15.3",
, though I'm sorry, I actually lied in my previous post - I'm not using typescript fully in this project, just some soft IDE JS checks. However, I checked the use
type and the second argument should always be of the RequestHandler
type, so I wonder why it tries to match Application
in your case 🤔
@rtrembecky thanks for the reply! I'm not sure, either. It doesn't have a type error if I use boltReceiver.app
as the second param instead.
In case it is helpful to anyone else: I had to resolve a separate issue after that though, where placing the app.use('/slack/events', boltReceiver.app);
anywhere after app.use(express.json());
it wouldn't work. I was getting a vague error about the data not being in the expected format and it returning early. The solution was to just apply express.json()
to the routes it was needed for, ex: app.post('/foo/bar', express.json(), async (req, res, next) => { ... });
. It may also work to just use slack before your express.json() use, but that wasn't an option in my case.
👋 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.
I wanted to show a pattern that worked for me in nestjs while I was working on solving this. Hopefully this helps.
I define a slack service:
export class SlackService {
private boltApp: App;
private readonly receiver: ExpressReceiver;
constructor(private appService: AppService) {
this.receiver = new ExpressReceiver({
signingSecret: process.env.SLACK_SIGNING_SECRET,
endpoints: '/',
});
this.boltApp = new App({
token: process.env.SLACK_BOT_TOKEN,
receiver: this.receiver
});
this.boltApp.event("app_mention", this.onAppMention.bind(this));
}
public async onAppMention({ event, client, logger }) {
try {
console.log(this);
console.log(event);
this.appService.doSomething();
} catch (error) {
logger.error(error);
}
}
public use(): Application {
return this.receiver.app;
}
}
And then in my main.js where I'm initializing the app module, I reference it like this:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const port = process.env.PORT || 3333;
const slack = app.get(SlackService);
app.use('/slack/events', slack.use());
await app.listen(port);
Logger.log(
`🚀 Application is running on: http://localhost:${port}/`
);
}
has anyone found another solution to the problem stated by @zaclittleberry? Specifically, the broken stream/body reading if express.json()
is used before the bolt receiver?
Update: replacing router.use(bodyParser.json());
with router.use(express.json());
did the trick
Update 2: nope, it did not.
Update 3: Found a workaround solution, to force Express to skip the Json middleware for the slack routes.
router.use(/\/((?!slack).)*/, express.json());
Updating the above.
I seems to work fine for Events, but now that I plugged in a "action" listener, call triggered by actions result in the stream not readable
errors.
What I have found but cannot fully understand yet is the different between the readableStates:
// Valid Request
flowing: null,
ended: false,
endEmitted: false,
sync: true,
readingMore: true,
dataEmitted: false,
//Invalid Request
flowing: true,
ended: true,
endEmitted: true,
sync: false,
readingMore: false,
dataEmitted: true,
Update 1:
Looks like requests from Action stuff are form-urlencodedand not
json`. Digging from there.
Update 2: Its not just the json parser, its also the urlencoder, so this now solves my issue:
router.use(/\/((?!slack).)*/, express.json());
router.use(/\/((?!slack).)*/, bodyParser.urlencoded({ extended: true }));
I hope this helps more people struggling with this.
I wanted to show a pattern that worked for me in nestjs while I was working on solving this. Hopefully this helps.
I define a slack service:
export class SlackService { private boltApp: App; private readonly receiver: ExpressReceiver; constructor(private appService: AppService) { this.receiver = new ExpressReceiver({ signingSecret: process.env.SLACK_SIGNING_SECRET, endpoints: '/', }); this.boltApp = new App({ token: process.env.SLACK_BOT_TOKEN, receiver: this.receiver }); this.boltApp.event("app_mention", this.onAppMention.bind(this)); } public async onAppMention({ event, client, logger }) { try { console.log(this); console.log(event); this.appService.doSomething(); } catch (error) { logger.error(error); } } public use(): Application { return this.receiver.app; } }
And then in my main.js where I'm initializing the app module, I reference it like this:
async function bootstrap() { const app = await NestFactory.create(AppModule); const port = process.env.PORT || 3333; const slack = app.get(SlackService); app.use('/slack/events', slack.use()); await app.listen(port); Logger.log( `🚀 Application is running on: http://localhost:${port}/` ); }
Thanks so much for this, it was a lifesaver!
By the way, if anyone else is using this as well and is wondering why /slack/install
isn't working, it's because it's being nested as /slack/events/slack/install
, which isn't great. To fix this, I modified the above to use app.use('/', slack.use())
and removed endpoints: '/'
from the express receiver configuration.
I wanted to show a pattern that worked for me in nestjs while I was working on solving this. Hopefully this helps. I define a slack service:
export class SlackService { private boltApp: App; private readonly receiver: ExpressReceiver; constructor(private appService: AppService) { this.receiver = new ExpressReceiver({ signingSecret: process.env.SLACK_SIGNING_SECRET, endpoints: '/', }); this.boltApp = new App({ token: process.env.SLACK_BOT_TOKEN, receiver: this.receiver }); this.boltApp.event("app_mention", this.onAppMention.bind(this)); } public async onAppMention({ event, client, logger }) { try { console.log(this); console.log(event); this.appService.doSomething(); } catch (error) { logger.error(error); } } public use(): Application { return this.receiver.app; } }
And then in my main.js where I'm initializing the app module, I reference it like this:
async function bootstrap() { const app = await NestFactory.create(AppModule); const port = process.env.PORT || 3333; const slack = app.get(SlackService); app.use('/slack/events', slack.use()); await app.listen(port); Logger.log( `🚀 Application is running on: http://localhost:${port}/` ); }
Thanks so much for this, it was a lifesaver!
By the way, if anyone else is using this as well and is wondering why
/slack/install
isn't working, it's because it's being nested as/slack/events/slack/install
, which isn't great. To fix this, I modified the above to useapp.use('/', slack.use())
and removedendpoints: '/'
from the express receiver configuration.
Did anyone manage to implement an installation store with this solution? It seems when you do, it doesn't inject the token into requests.
Did anyone manage to implement an installation store with this solution? It seems when you do, it doesn't inject the token into requests.
@n6rayan Any updates since you had this issue? I'm facing exactly that with my NestJS and Bolt-js set-up via app.use('/', slack.use())
. Have failed to figure it out so far. @siawyoung Do you remember if you had a similar issue in your setup?
UPD: have managed to sort it out. It's essential to maintain the same data structure in production installationStore that is used in FileInstallationStore. Also, it's important to clean up older installations from DB, as some can be duplicates with invalid token.
I am very new to all this, and really have no idea what I am doing. But I have been tasked to add a slack app to an existing web app. And I am trying to follow the examples in this thread.
First I get:
`import { App, ExpressReceiver } from '@slack/bolt';
^^^
SyntaxError: Named export 'App' not found. The requested module '@slack/bolt' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '@slack/bolt';
const { App, ExpressReceiver } = pkg;`
Then if I do:
import App from '@slack/bolt';
import ExpressReceiver from '@slack/bolt';
...
const receiver = new ExpressReceiver({
^
TypeError: ExpressReceiver is not a constructor
same error when trying to create App
object.
It's like I am missing something very basic, everyone else seems to be able to have it work with code like above :(
Also, I want to enable socketMode so I can have slack send me the events/mentions/commands to this webapp. I am able to get the events going when creating a completely separate slack bolt app (from the tutorials).
Any pointers will be greatly appreciated.
@shikhanshu this looks like a project setup issue, not really related to this. You can google for this error message (omitting the package specifics) and you'll find many results. But you should also be able to follow the error message suggestion and do exactly what's written there:
import bolt from '@slack/bolt'
const { App, ExpressReceiver} = bolt
I would like to use bolt as an express middleware similar to how the @slack/events-api work.
app.use('/slack/events', slackEvents.expressMiddleware());
Is there an equivalent in bolt? Thanks