@chat-adapter/slack 4.13.0 → 4.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # @chat-adapter/slack
2
2
 
3
- Slack adapter for the [chat](https://github.com/vercel-labs/chat) SDK.
3
+ [![npm version](https://img.shields.io/npm/v/@chat-adapter/slack)](https://www.npmjs.com/package/@chat-adapter/slack)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@chat-adapter/slack)](https://www.npmjs.com/package/@chat-adapter/slack)
5
+
6
+ Slack adapter for [Chat SDK](https://chat-sdk.dev/docs). Supports single-workspace and multi-workspace OAuth deployments.
4
7
 
5
8
  ## Installation
6
9
 
@@ -8,13 +11,13 @@ Slack adapter for the [chat](https://github.com/vercel-labs/chat) SDK.
8
11
  npm install chat @chat-adapter/slack
9
12
  ```
10
13
 
11
- ## Usage (Single Workspace)
14
+ ## Usage
12
15
 
13
16
  ```typescript
14
17
  import { Chat } from "chat";
15
18
  import { createSlackAdapter } from "@chat-adapter/slack";
16
19
 
17
- const chat = new Chat({
20
+ const bot = new Chat({
18
21
  userName: "mybot",
19
22
  adapters: {
20
23
  slack: createSlackAdapter({
@@ -24,203 +27,14 @@ const chat = new Chat({
24
27
  },
25
28
  });
26
29
 
27
- // Handle @mentions
28
- chat.onNewMention(async (thread, message) => {
30
+ bot.onNewMention(async (thread, message) => {
29
31
  await thread.post("Hello from Slack!");
30
32
  });
31
33
  ```
32
34
 
33
- ## Multi-Workspace Mode
34
-
35
- For apps installed across multiple Slack workspaces, omit `botToken` and let the adapter resolve tokens dynamically from your state adapter (e.g. Redis) using the `team_id` from incoming webhooks.
36
-
37
- ```typescript
38
- import { Chat } from "chat";
39
- import { createSlackAdapter } from "@chat-adapter/slack";
40
- import { createRedisState } from "@chat-adapter/state-redis";
41
-
42
- const slackAdapter = createSlackAdapter({
43
- signingSecret: process.env.SLACK_SIGNING_SECRET!,
44
- clientId: process.env.SLACK_CLIENT_ID!,
45
- clientSecret: process.env.SLACK_CLIENT_SECRET!,
46
- logger: logger,
47
- encryptionKey: process.env.SLACK_ENCRYPTION_KEY, // optional, encrypts tokens at rest
48
- });
49
-
50
- const chat = new Chat({
51
- userName: "mybot",
52
- adapters: { slack: slackAdapter },
53
- state: createRedisState({ url: process.env.REDIS_URL! }),
54
- // notice that there is no bot token
55
- });
56
- ```
57
-
58
- ### OAuth callback
59
-
60
- The adapter handles the full Slack OAuth V2 exchange. Pass `clientId` and `clientSecret` in the config, then point your OAuth redirect URL to a route that calls `handleOAuthCallback`:
61
-
62
- ```typescript
63
- import { slackAdapter } from "@/lib/chat"; // your adapter instance
64
-
65
- export async function GET(request: Request) {
66
- const { teamId } = await slackAdapter.handleOAuthCallback(request);
67
- return new Response(`Installed for team ${teamId}!`);
68
- }
69
- ```
70
-
71
- ### Webhook handling
72
-
73
- No changes needed — the adapter extracts `team_id` from incoming webhooks and resolves the token automatically:
74
-
75
- ```typescript
76
- export async function POST(request: Request) {
77
- return chat.webhooks.slack(request, { waitUntil });
78
- }
79
- ```
80
-
81
- ### Using the adapter outside a webhook (cron jobs, workflows)
82
-
83
- During webhook handling, the adapter resolves the token automatically from `team_id`. Outside that context (e.g. a cron job), use `getInstallation` to retrieve the token and `withBotToken` to scope it:
84
-
85
- ```typescript
86
- import { Chat } from "chat";
87
-
88
- // In a cron job or background worker:
89
- const install = await slackAdapter.getInstallation(teamId);
90
- if (!install) throw new Error("Workspace not installed");
91
-
92
- await slackAdapter.withBotToken(install.botToken, async () => {
93
- // All adapter calls inside this callback use the provided token.
94
- // You can use thread.post(), thread.subscribe(), etc. normally.
95
- const thread = chat.thread("slack:C12345:1234567890.123456");
96
- await thread.post("Hello from a cron job!");
97
- });
98
- ```
99
-
100
- `withBotToken` uses `AsyncLocalStorage` under the hood, so concurrent calls with different tokens are isolated from each other.
101
-
102
- ### Removing installations
103
-
104
- ```typescript
105
- await slackAdapter.deleteInstallation(teamId);
106
- ```
107
-
108
- ### Encryption
109
-
110
- Pass a base64-encoded 32-byte key as `encryptionKey` to encrypt bot tokens at rest using AES-256-GCM. You can generate a key with:
111
-
112
- ```bash
113
- openssl rand -base64 32
114
- ```
115
-
116
- When `encryptionKey` is set, `setInstallation()` encrypts the token before storing it and `getInstallation()` decrypts it transparently.
117
-
118
- ## Configuration
119
-
120
- | Option | Required | Description |
121
- |--------|----------|-------------|
122
- | `botToken` | No | Slack bot token (`xoxb-...`). Required for single-workspace mode. Omit for multi-workspace. |
123
- | `signingSecret` | Yes | Slack signing secret for webhook verification |
124
- | `clientId` | No | Slack app client ID (required for OAuth / multi-workspace) |
125
- | `clientSecret` | No | Slack app client secret (required for OAuth / multi-workspace) |
126
- | `encryptionKey` | No | Base64-encoded 32-byte AES-256-GCM key for encrypting stored tokens |
127
-
128
- ## Environment Variables
129
-
130
- ```bash
131
- SLACK_BOT_TOKEN=xoxb-... # single-workspace only
132
- SLACK_SIGNING_SECRET=...
133
- SLACK_CLIENT_ID=... # required for multi-workspace OAuth
134
- SLACK_CLIENT_SECRET=... # required for multi-workspace OAuth
135
- SLACK_ENCRYPTION_KEY=... # optional, for multi-workspace token encryption
136
- ```
137
-
138
- ## Slack App Setup
139
-
140
- ### 1. Create a Slack App
141
-
142
- 1. Go to [api.slack.com/apps](https://api.slack.com/apps)
143
- 2. Click **Create New App** → **From scratch**
144
- 3. Enter app name and select workspace
145
- 4. Click **Create App**
146
-
147
- ### 2. Configure Bot Token Scopes
148
-
149
- 1. Go to **OAuth & Permissions** in the sidebar
150
- 2. Under **Scopes** → **Bot Token Scopes**, add:
151
- - `app_mentions:read` - Receive @mention events
152
- - `channels:history` - Read messages in public channels
153
- - `channels:read` - View basic channel info
154
- - `chat:write` - Send messages
155
- - `groups:history` - Read messages in private channels
156
- - `groups:read` - View basic private channel info
157
- - `im:history` - Read direct messages
158
- - `im:read` - View basic DM info
159
- - `reactions:read` - View emoji reactions
160
- - `reactions:write` - Add/remove emoji reactions
161
- - `users:read` - View user info (for display names)
162
-
163
- ### 3. Install App to Workspace
164
-
165
- **Single workspace:** Install directly from the Slack dashboard.
166
-
167
- 1. Go to **OAuth & Permissions**
168
- 2. Click **Install to Workspace**
169
- 3. Authorize the app
170
- 4. Copy the **Bot User OAuth Token** (starts with `xoxb-`) → `SLACK_BOT_TOKEN`
171
-
172
- **Multi-workspace:** Enable **Manage Distribution** under **Basic Information**, then set up an [OAuth redirect URL](https://api.slack.com/authentication/oauth-v2) pointing to your callback route. The adapter handles the token exchange via `handleOAuthCallback()` (see [Multi-Workspace Mode](#multi-workspace-mode) above).
173
-
174
- ### 4. Get Signing Secret and OAuth Credentials
175
-
176
- 1. Go to **Basic Information**
177
- 2. Under **App Credentials**, copy:
178
- - **Signing Secret** → `SLACK_SIGNING_SECRET`
179
- - **Client ID** → `SLACK_CLIENT_ID` (multi-workspace only)
180
- - **Client Secret** → `SLACK_CLIENT_SECRET` (multi-workspace only)
181
-
182
- ### 5. Configure Event Subscriptions
183
-
184
- 1. Go to **Event Subscriptions**
185
- 2. Toggle **Enable Events** to On
186
- 3. Set **Request URL** to: `https://your-domain.com/api/webhooks/slack`
187
- - Slack will verify the URL immediately
188
- 4. Under **Subscribe to bot events**, add:
189
- - `app_mention` - When someone @mentions your bot
190
- - `message.channels` - Messages in public channels
191
- - `message.groups` - Messages in private channels
192
- - `message.im` - Direct messages
193
- 5. Click **Save Changes**
194
-
195
- ### 6. (Optional) Enable Interactivity
196
-
197
- If you want to use buttons, modals, or other interactive components:
198
-
199
- 1. Go to **Interactivity & Shortcuts**
200
- 2. Toggle **Interactivity** to On
201
- 3. Set **Request URL** to: `https://your-domain.com/api/webhooks/slack`
202
-
203
- ## Features
204
-
205
- - Multi-workspace support with OAuth V2 and encrypted token storage
206
- - Message posting and editing
207
- - Thread subscriptions
208
- - Reaction handling (add/remove/events)
209
- - File attachments
210
- - Rich cards (Block Kit)
211
- - Action callbacks (interactive components)
212
- - Direct messages
213
-
214
- ## Troubleshooting
215
-
216
- ### "Invalid signature" error
217
- - Verify `SLACK_SIGNING_SECRET` is correct
218
- - Check that the request timestamp is within 5 minutes (clock sync issue)
35
+ ## Documentation
219
36
 
220
- ### Bot not responding to messages
221
- - Verify Event Subscriptions are configured
222
- - Check that the bot has been added to the channel
223
- - Ensure the webhook URL is correct and accessible
37
+ Full setup instructions, configuration reference, and features at [chat-sdk.dev/docs/adapters/slack](https://chat-sdk.dev/docs/adapters/slack).
224
38
 
225
39
  ## License
226
40
 
package/dist/index.d.ts CHANGED
@@ -8,8 +8,8 @@ import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, Logger,
8
8
  */
9
9
 
10
10
  interface SlackBlock {
11
- type: string;
12
11
  block_id?: string;
12
+ type: string;
13
13
  [key: string]: unknown;
14
14
  }
15
15
  /**
@@ -23,8 +23,8 @@ declare function cardToBlockKit(card: CardElement): SlackBlock[];
23
23
  declare function cardToFallbackText(card: CardElement): string;
24
24
 
25
25
  interface EncryptedTokenData {
26
- iv: string;
27
26
  data: string;
27
+ iv: string;
28
28
  tag: string;
29
29
  }
30
30
  declare function decodeKey(rawKey: string): Buffer;
@@ -65,23 +65,23 @@ declare class SlackFormatConverter extends BaseFormatConverter {
65
65
  interface SlackAdapterConfig {
66
66
  /** Bot token (xoxb-...). Required for single-workspace mode. Omit for multi-workspace. */
67
67
  botToken?: string;
68
- /** Signing secret for webhook verification */
69
- signingSecret: string;
70
- /** Logger instance for error reporting */
71
- logger: Logger;
72
- /** Override bot username (optional) */
73
- userName?: string;
74
68
  /** Bot user ID (will be fetched if not provided) */
75
69
  botUserId?: string;
70
+ /** Slack app client ID (required for OAuth / multi-workspace) */
71
+ clientId?: string;
72
+ /** Slack app client secret (required for OAuth / multi-workspace) */
73
+ clientSecret?: string;
76
74
  /**
77
75
  * Base64-encoded 32-byte AES-256-GCM encryption key.
78
76
  * If provided, bot tokens stored via setInstallation() will be encrypted at rest.
79
77
  */
80
78
  encryptionKey?: string;
81
- /** Slack app client ID (required for OAuth / multi-workspace) */
82
- clientId?: string;
83
- /** Slack app client secret (required for OAuth / multi-workspace) */
84
- clientSecret?: string;
79
+ /** Logger instance for error reporting */
80
+ logger: Logger;
81
+ /** Signing secret for webhook verification */
82
+ signingSecret: string;
83
+ /** Override bot username (optional) */
84
+ userName?: string;
85
85
  }
86
86
  /** Data stored per Slack workspace installation */
87
87
  interface SlackInstallation {
@@ -96,20 +96,13 @@ interface SlackThreadId {
96
96
  }
97
97
  /** Slack event payload (raw message format) */
98
98
  interface SlackEvent {
99
- type: string;
100
- user?: string;
101
99
  bot_id?: string;
102
100
  channel?: string;
103
- text?: string;
104
- ts?: string;
105
- thread_ts?: string;
106
- subtype?: string;
107
- username?: string;
101
+ /** Channel type: "channel", "group", "mpim", or "im" (DM) */
102
+ channel_type?: string;
108
103
  edited?: {
109
104
  ts: string;
110
105
  };
111
- /** Channel type: "channel", "group", "mpim", or "im" (DM) */
112
- channel_type?: string;
113
106
  files?: Array<{
114
107
  id?: string;
115
108
  mimetype?: string;
@@ -119,42 +112,49 @@ interface SlackEvent {
119
112
  original_w?: number;
120
113
  original_h?: number;
121
114
  }>;
122
- team?: string;
123
- team_id?: string;
124
- /** Number of replies in the thread (present on thread parent messages) */
125
- reply_count?: number;
126
115
  /** Timestamp of the latest reply (present on thread parent messages) */
127
116
  latest_reply?: string;
117
+ /** Number of replies in the thread (present on thread parent messages) */
118
+ reply_count?: number;
119
+ subtype?: string;
120
+ team?: string;
121
+ team_id?: string;
122
+ text?: string;
123
+ thread_ts?: string;
124
+ ts?: string;
125
+ type: string;
126
+ user?: string;
127
+ username?: string;
128
128
  }
129
129
  /** Slack reaction event payload */
130
130
  interface SlackReactionEvent {
131
- type: "reaction_added" | "reaction_removed";
132
- user: string;
133
- reaction: string;
134
- item_user?: string;
131
+ event_ts: string;
135
132
  item: {
136
133
  type: string;
137
134
  channel: string;
138
135
  ts: string;
139
136
  };
140
- event_ts: string;
137
+ item_user?: string;
138
+ reaction: string;
139
+ type: "reaction_added" | "reaction_removed";
140
+ user: string;
141
141
  }
142
142
  declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
143
143
  readonly name = "slack";
144
144
  readonly userName: string;
145
- private client;
146
- private signingSecret;
147
- private defaultBotToken;
145
+ private readonly client;
146
+ private readonly signingSecret;
147
+ private readonly defaultBotToken;
148
148
  private chat;
149
- private logger;
149
+ private readonly logger;
150
150
  private _botUserId;
151
151
  private _botId;
152
- private formatConverter;
152
+ private readonly formatConverter;
153
153
  private static USER_CACHE_TTL_MS;
154
- private clientId;
155
- private clientSecret;
156
- private encryptionKey;
157
- private requestContext;
154
+ private readonly clientId;
155
+ private readonly clientSecret;
156
+ private readonly encryptionKey;
157
+ private readonly requestContext;
158
158
  /** Bot user ID (e.g., U_BOT_123) used for mention detection */
159
159
  get botUserId(): string | undefined;
160
160
  constructor(config: SlackAdapterConfig);