@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 +9 -195
- package/dist/index.d.ts +39 -39
- package/dist/index.js +72 -42
- package/dist/index.js.map +1 -1
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# @chat-adapter/slack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@chat-adapter/slack)
|
|
4
|
+
[](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
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
/**
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|