npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

slack-edge

v0.12.0

Published

Slack app development framework for edge functions with streamlined TypeScript support

Downloads

2,358

Readme

Slack Edge

npm version deno module

The slack-edge library is a Slack app development framework designed specifically for the following runtimes:

Not only does it work with the above, but it also functions with the latest versions of Deno, Bun, and Node.js.

This framework draws significant inspiration from Slack's Bolt framework, but its design does not strictly follow the bolt-js blueprint. Key differences include:

  • Edge function ready: Out-of-the-box edge function (e.g., Cloudflare Workers) support
  • TypeScript focused: Enhances type safety and clarifies typings for developers
  • Lazy listener enabled: bolt-python's lazy listener feature is provided out of the box
  • Zero additional dependencies: No other dependencies required beyond TypeScript types and slack-web-api-client (our fetch-function-based Slack API client)

Getting Started

Currently, you can use this library for three different platforms.

Cloudflare Workers

Cloudflare Workers is an edge function platform for quickly deploying an app.

The only additional thing you need to do is to add the slack-cloudflare-workers package to your Cloudflare Workers app project. The slack-cloudflare-workers package enhances slack-edge with Cloudflare Workers-specific features, such as KV-based installation stores.

npm install -g wrangler@latest
npx wrangler generate my-slack-app
cd my-slack-app
npm i slack-cloudflare-workers@latest

A simple app that handles a slash command can be structured like this:

import { SlackApp, SlackEdgeAppEnv } from "slack-cloudflare-workers";

export default {
  async fetch(
    request: Request,
    env: SlackEdgeAppEnv,
    ctx: ExecutionContext
  ): Promise<Response> {
    const app = new SlackApp({ env })
      .command("/hello-cf-workers",
        async (req) => {
          // sync handler, which is resposible to ack the request
          return ":wave: This app runs on Cloudflare Workers!";
          // If you don't have anything to do here, the function doesn't need to return anything
          // This means in that case, simply having `async () => {}` works for you
        },
        async ({ context: { respond } }) => {
          // Lazy listener, which can be executed asynchronously
          // You can do whatever may take longer than 3 seconds here
          await respond({ text: "This is an async reply. How are you doing?" });
        }
      );
    return await app.run(request, ctx);
  },
};

Also, before running this app, don't forget to create a .dev.vars file with your env variables for local development:

SLACK_SIGNING_SECRET=....
SLACK_BOT_TOKEN=xoxb-...
SLACK_LOGGING_LEVEL=DEBUG

When you run wrangler dev --port 3000, your app process spins up, and it starts handling API requests at http://localhost:3000/slack/events. You may want to run Cloudflare Tunnel or something equivalent to forward public endpoint requests to this local app process.

Refer to https://github.com/seratch/slack-cloudflare-workers/blob/main/docs/index.md for further guidance.

Vercel Edge Functions

Vercel Edge Functions is an edge function platform, which is part of Vercel's Frontend Cloud.

Create a new Next.js project w/ slack-edge library by following these steps:

npx create-next-app@latest --typescript
cd your-app-name
npm i slack-edge

Create a new source file pages/api/slack.ts that contains the following code:

import type { NextFetchEvent, NextRequest } from "next/server";
import { SlackApp } from "slack-edge";

export const config = {
  runtime: "edge",
};

const app = new SlackApp({
  env: {
    SLACK_SIGNING_SECRET: process.env.SLACK_SIGNING_SECRET!,
    SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN!,
    SLACK_LOGGING_LEVEL: "DEBUG",
  },
});

app.command("/hello-edge",
  async (req) => {
    // sync handler, which is resposible to ack the request
    return ":wave: This app runs on Vercel Edge Function platform!";
    // If you don't have anything to do here, the function doesn't need to return anything
    // This means in that case, simply having `async () => {}` works for you
  },
  async ({ context: { respond } }) => {
    // Lazy listener, which can be executed asynchronously
    // You can do whatever may take longer than 3 seconds here
    await respond({ text: "This is an async reply. How are you doing?" });
  }
);

export default async function handler(
  req: NextRequest,
  context: NextFetchEvent
) {
  return await app.run(req, context);
}

With this code, your Request URL for handling requests from Slack API server will be https://{your public domain}/api/slack instead of /slack/events.

Lastly, you can run the app by following these steps:

export SLACK_SIGNING_SECRET="..."
export SLACK_BOT_TOKEN="xoxb-..."
npm run dev

If you use Cloudflare Tunnel or something equivalent, you may want to run cloudflared tunnel --url http://localhost:3000 in a different terminal window.

We are currently working on the slack-vercel-edge-functions package, which offers Vercel-specific features in addition to the core ones provided by slack-edge. Until its release, please use slack-edge directly and implement the missing features on your own as necessary.

Supabase Edge Functions

Supabase is a BaaS that provides a full PostgreSQL database, along with other products such as Supabase Edge Functions.

Create a new Supabase project locally by following these steps:

mkdir myapp
cd myapp

brew install supabase/tap/supabase
supabase init
supabase functions new hello-world

Open the source file supabase/functions/hello-world/index.ts and replace it with the following:

import {
  SlackApp,
  SlackEdgeAppEnv,
} from "https://deno.land/x/[email protected]/mod.ts";

const app = new SlackApp<SlackEdgeAppEnv>({
  env: {
    SLACK_SIGNING_SECRET: Deno.env.get("SLACK_SIGNING_SECRET")!,
    SLACK_BOT_TOKEN: Deno.env.get("SLACK_BOT_TOKEN"),
    SLACK_LOGGING_LEVEL: "DEBUG",
  },
});

app.command("/hello-edge", async (req) => {
  // sync handler, which is resposible to ack the request
  return ":wave: This app runs on Vercel Edge Function platform!";
  // If you don't have anything to do here, the function doesn't need to return anything
  // This means in that case, simply having `async () => {}` works for you
}, async ({ context: { respond } }) => {
  // Lazy listener, which can be executed asynchronously
  // You can do whatever may take longer than 3 seconds here
  await respond({ text: "This is an async reply. How are you doing?" });
});

Deno.serve(async (req) => {
  return await app.run(req);
});

Run locally

Create a supabase/functions/.env file with the following:

export SLACK_SIGNING_SECRET="..."
export SLACK_BOT_TOKEN="xoxb-..."

Then run:

# Terminal A
supabase start
supabase functions serve --env-file supabase/functions/.env

# Terminal B
brew install cloudflare/cloudflare/cloudflared
cloudflared tunnel --url http://127.0.0.1:54321

Run in the cloud

In a browser, login to your Supabase account and create a new project.

Then open a terminal in your myapp's project directory and run:

supabase login

Link the Supabase project you just made:

supabase link

Deploy your hello-world edge function.

supabase functions deploy hello-world --no-verify-jwt

Finally, add your environment variables:

  • Go to your Supabase project
  • Go to the Edge Functions tab, and click on your edge functions
  • Tap on Manage Secrets and then add your SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET

Run Your App with Deno / Bun / Node.js

This library is available not only for edge function use cases but also for any JavaScript runtime use cases. Specifically, you can run an app with Deno, Bun, and Node.js. To learn more about this, please check the example files under the ./test directory.

Run with Bun

import { SlackApp } from "slack-edge";

const app = new SlackApp({
  env: {
    SLACK_SIGNING_SECRET: process.env.SLACK_SIGNING_SECRET!,
    SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN,
    SLACK_LOGGING_LEVEL: "DEBUG",
  },
});

// Add listeners here

export default {
  port: 3000,
  async fetch(request) {
    return await app.run(request);
  },
};

You can run the app by:

# Terminal A
bun run --watch my-app.ts
# Terminal B
brew install cloudflare/cloudflare/cloudflared
cloudflared tunnel --url http://localhost:3000

Run with Deno

Please refer to README for the Deno module.

Run with Node.js (Socket Mode: Production-ready)

If you need a stable Socket Mode integration, we recommend using @slack/socket-mode along with this package. With Node 20+, the following example works for you:

// npm i slack-edge @slack/socket-mode
import {
  SlackApp,
  fromSocketModeToRequest,
  fromResponseToSocketModePayload,
} from "slack-edge";
import { SocketModeClient } from "@slack/socket-mode";
import { LogLevel } from "@slack/logger";

const app = new SlackApp({
  socketMode: true,
  env: {
    SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN!,
    SLACK_APP_TOKEN: process.env.SLACK_APP_TOKEN!,
    SLACK_LOGGING_LEVEL: "DEBUG",
  },
});

// Add listeners here
app.command("/hello", async ({}) => {
  return "Hi!";
});

// Start a Socket Mode client
const socketModeClient = new SocketModeClient({
  appToken: process.env.SLACK_APP_TOKEN!,
  logLevel: LogLevel.DEBUG,
});
socketModeClient.on("slack_event", async ({ body, ack, retry_num: retryNum, retry_reason: retryReason }) => {
  const request = fromSocketModeToRequest({ body, retryNum, retryReason });
  if (!request) { return; }
  const response = await app.run(request);
  await ack(await fromResponseToSocketModePayload({ response }));
},);
(async () => {
  await socketModeClient.start();
})();

If you want to build a Slack app this way, this project template should be pretty useful for you!

Run with Remix + Node.js

If you're looking for a way to serve Slack app using Remix, slack-edge is the best way to go! Here is a simple example to run your Slack app as part of a Remix app:

import { LoaderFunctionArgs } from "@remix-run/node";
import { SlackApp } from "slack-edge";

export async function action({ request }: LoaderFunctionArgs) {
  const app = new SlackApp({
    env: {
      SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN,
      SLACK_SIGNING_SECRET: process.env.SLACK_SIGNING_SECRET!,
    }
  });
  // Add listeners here

  return await app.run(request);
}

You can run the app by following these steps:

  • npx create-remix@latest
  • cd my-remix-app
  • npm i @remix-run/serve @remix-run/node slack-edge
  • Add app/routes/api.slack.tsx (if you go with POST /api/slack) with the above code
  • export SLACK_SIGNING_SECRET=...
  • export SLACK_BOT_TOKEN=xoxb-...
  • npm run dev

Reference

Middleware

This framework offers ways to globally customize your app's behavior, like you do when developing web apps. A common example is to attach extra data to the context object for following listeners.

|Pattern|Description| |-|-| |app.beforeAuthorize|The passed function does something before calling authorize() function. If this method returns SlackResponse, the following middleware and listeners won't be executed.| |app.afterAuthorize / app.use / app.middleware|The passed function does something right after calling authorize() function. If this method returns SlackResponse, the following middleware and listeners won't be executed.|

ack / lazy Functions

You may be unfamiliar with the "lazy listener" concept in this framework. To learn more about it, please read bolt-python's documentation: https://slack.dev/bolt-python/concepts#lazy-listeners

The ack function must complete within 3 seconds, while the lazy function can perform time-consuming tasks. It's important to note that not all request handlers support the ack or lazy functions. For more information, please refer to the following table, which covers all the patterns in detail.

Starting from v0.9, if desired, you can execute lazy listeners after the completion of their ack function. To customize the behavior in this manner, you can pass startLazyListenerAfterAck: true as one of the arguments in the App constructor.

|Pattern|Description|ack function|lazy function| |-|-|-|-| |app.command|The passed function handles a slash command request pattern. If the function returns a message, the message will be posted in the channel where the end-user invoked the command.|◯|◯| |app.event|The passed function asynchronously does something when an Events API request that matches the constraints comes in. Please note that manually acknowledge a request is unsupported. You can pass only one function, which can be executed as a lazy listener.|x|◯| |app.function [experimental]|The passed function asynchronously does something when a "function_executed" event request that matches the constraints comes in. Please note that manually acknowledge a request is unsupported. You can pass only one function, which can be executed as a lazy listener. Also, this feature is still in beta so that the details could be changed on the Slack plaform side until it's GAed.|x|◯| |app.message / app.anyMessage|The passed function asynchronously does something when an a message event comes in. Please note that manually acknowledge a request is unsupported. You can pass only one function, which can be executed as a lazy listener. If the message pattern argument can be any of string, regexp, and undefined. When you pass undefined, the listener matches all messages.|x|◯| |app.shortcut / app.globalShortcut / app.messageShortcut|The passed function handles a global/message shortcut request pattern. Please note that returning a message text in the ack function does not work for shortcuts. Instead, you can use context.respond for it.|◯|◯| |app.action|The passed function handles a user interaction on a Block Kit component such as button clicks, item selection in a select menu, and so on.|◯|◯| |app.options|The passed function handles an external data source reqeust for Block Kit select menus. You cannnot respond to this request pattern asynchronously, so slack-edge enables developers to pass only ack function, which must complete within 3 seconds, here.|◯|x| |app.view / app.viewSubmission / app.viewClosed|The passed function handles either a modal data submission or the "Close" button click event. ack function can return various response_actions (errors, update, push, clear) and their associated data. If you want to simply close the modal, you don't need to return anything.|◯|◯|