@chat-adapter/slack 4.8.0 → 4.9.0
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 +104 -5
- package/dist/index.d.ts +78 -4
- package/dist/index.js +418 -95
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Slack adapter for the [chat](https://github.com/vercel-labs/chat) SDK.
|
|
|
8
8
|
npm install chat @chat-adapter/slack
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## Usage
|
|
11
|
+
## Usage (Single Workspace)
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Chat } from "chat";
|
|
@@ -30,18 +30,109 @@ chat.onNewMention(async (thread, message) => {
|
|
|
30
30
|
});
|
|
31
31
|
```
|
|
32
32
|
|
|
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
|
+
|
|
33
118
|
## Configuration
|
|
34
119
|
|
|
35
120
|
| Option | Required | Description |
|
|
36
121
|
|--------|----------|-------------|
|
|
37
|
-
| `botToken` |
|
|
122
|
+
| `botToken` | No | Slack bot token (`xoxb-...`). Required for single-workspace mode. Omit for multi-workspace. |
|
|
38
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 |
|
|
39
127
|
|
|
40
128
|
## Environment Variables
|
|
41
129
|
|
|
42
130
|
```bash
|
|
43
|
-
SLACK_BOT_TOKEN=xoxb-...
|
|
131
|
+
SLACK_BOT_TOKEN=xoxb-... # single-workspace only
|
|
44
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
|
|
45
136
|
```
|
|
46
137
|
|
|
47
138
|
## Slack App Setup
|
|
@@ -71,15 +162,22 @@ SLACK_SIGNING_SECRET=...
|
|
|
71
162
|
|
|
72
163
|
### 3. Install App to Workspace
|
|
73
164
|
|
|
165
|
+
**Single workspace:** Install directly from the Slack dashboard.
|
|
166
|
+
|
|
74
167
|
1. Go to **OAuth & Permissions**
|
|
75
168
|
2. Click **Install to Workspace**
|
|
76
169
|
3. Authorize the app
|
|
77
170
|
4. Copy the **Bot User OAuth Token** (starts with `xoxb-`) → `SLACK_BOT_TOKEN`
|
|
78
171
|
|
|
79
|
-
|
|
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
|
|
80
175
|
|
|
81
176
|
1. Go to **Basic Information**
|
|
82
|
-
2. Under **App Credentials**, copy
|
|
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)
|
|
83
181
|
|
|
84
182
|
### 5. Configure Event Subscriptions
|
|
85
183
|
|
|
@@ -104,6 +202,7 @@ If you want to use buttons, modals, or other interactive components:
|
|
|
104
202
|
|
|
105
203
|
## Features
|
|
106
204
|
|
|
205
|
+
- Multi-workspace support with OAuth V2 and encrypted token storage
|
|
107
206
|
- Message posting and editing
|
|
108
207
|
- Thread subscriptions
|
|
109
208
|
- Reaction handling (add/remove/events)
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,13 @@ declare function cardToBlockKit(card: CardElement): SlackBlock[];
|
|
|
22
22
|
*/
|
|
23
23
|
declare function cardToFallbackText(card: CardElement): string;
|
|
24
24
|
|
|
25
|
+
interface EncryptedTokenData {
|
|
26
|
+
iv: string;
|
|
27
|
+
data: string;
|
|
28
|
+
tag: string;
|
|
29
|
+
}
|
|
30
|
+
declare function decodeKey(rawKey: string): Buffer;
|
|
31
|
+
|
|
25
32
|
/**
|
|
26
33
|
* Slack-specific format conversion using AST-based parsing.
|
|
27
34
|
*
|
|
@@ -56,8 +63,8 @@ declare class SlackFormatConverter extends BaseFormatConverter {
|
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
interface SlackAdapterConfig {
|
|
59
|
-
/** Bot token (xoxb-...) */
|
|
60
|
-
botToken
|
|
66
|
+
/** Bot token (xoxb-...). Required for single-workspace mode. Omit for multi-workspace. */
|
|
67
|
+
botToken?: string;
|
|
61
68
|
/** Signing secret for webhook verification */
|
|
62
69
|
signingSecret: string;
|
|
63
70
|
/** Logger instance for error reporting */
|
|
@@ -66,6 +73,21 @@ interface SlackAdapterConfig {
|
|
|
66
73
|
userName?: string;
|
|
67
74
|
/** Bot user ID (will be fetched if not provided) */
|
|
68
75
|
botUserId?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Base64-encoded 32-byte AES-256-GCM encryption key.
|
|
78
|
+
* If provided, bot tokens stored via setInstallation() will be encrypted at rest.
|
|
79
|
+
*/
|
|
80
|
+
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;
|
|
85
|
+
}
|
|
86
|
+
/** Data stored per Slack workspace installation */
|
|
87
|
+
interface SlackInstallation {
|
|
88
|
+
botToken: string;
|
|
89
|
+
botUserId?: string;
|
|
90
|
+
teamName?: string;
|
|
69
91
|
}
|
|
70
92
|
/** Slack-specific thread ID data */
|
|
71
93
|
interface SlackThreadId {
|
|
@@ -118,23 +140,75 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
118
140
|
readonly userName: string;
|
|
119
141
|
private client;
|
|
120
142
|
private signingSecret;
|
|
121
|
-
private
|
|
143
|
+
private defaultBotToken;
|
|
122
144
|
private chat;
|
|
123
145
|
private logger;
|
|
124
146
|
private _botUserId;
|
|
125
147
|
private _botId;
|
|
126
148
|
private formatConverter;
|
|
127
149
|
private static USER_CACHE_TTL_MS;
|
|
150
|
+
private clientId;
|
|
151
|
+
private clientSecret;
|
|
152
|
+
private encryptionKey;
|
|
153
|
+
private requestContext;
|
|
128
154
|
/** Bot user ID (e.g., U_BOT_123) used for mention detection */
|
|
129
155
|
get botUserId(): string | undefined;
|
|
130
156
|
constructor(config: SlackAdapterConfig);
|
|
157
|
+
/**
|
|
158
|
+
* Get the current bot token for API calls.
|
|
159
|
+
* Checks request context (multi-workspace) → default token (single-workspace) → throws.
|
|
160
|
+
*/
|
|
161
|
+
private getToken;
|
|
162
|
+
/**
|
|
163
|
+
* Add the current token to API call options.
|
|
164
|
+
* Workaround for Slack WebClient types not including `token` in per-method args.
|
|
165
|
+
*/
|
|
166
|
+
private withToken;
|
|
131
167
|
initialize(chat: ChatInstance): Promise<void>;
|
|
168
|
+
private installationKey;
|
|
169
|
+
/**
|
|
170
|
+
* Save a Slack workspace installation.
|
|
171
|
+
* Call this from your OAuth callback route after a successful installation.
|
|
172
|
+
*/
|
|
173
|
+
setInstallation(teamId: string, installation: SlackInstallation): Promise<void>;
|
|
174
|
+
/**
|
|
175
|
+
* Retrieve a Slack workspace installation.
|
|
176
|
+
*/
|
|
177
|
+
getInstallation(teamId: string): Promise<SlackInstallation | null>;
|
|
178
|
+
/**
|
|
179
|
+
* Handle the Slack OAuth V2 callback.
|
|
180
|
+
* Accepts the incoming request, extracts the authorization code,
|
|
181
|
+
* exchanges it for tokens, and saves the installation.
|
|
182
|
+
*/
|
|
183
|
+
handleOAuthCallback(request: Request): Promise<{
|
|
184
|
+
teamId: string;
|
|
185
|
+
installation: SlackInstallation;
|
|
186
|
+
}>;
|
|
187
|
+
/**
|
|
188
|
+
* Remove a Slack workspace installation.
|
|
189
|
+
*/
|
|
190
|
+
deleteInstallation(teamId: string): Promise<void>;
|
|
191
|
+
/**
|
|
192
|
+
* Run a function with a specific bot token in context.
|
|
193
|
+
* Use this for operations outside webhook handling (cron jobs, workflows).
|
|
194
|
+
*/
|
|
195
|
+
withBotToken<T>(token: string, fn: () => T): T;
|
|
196
|
+
/**
|
|
197
|
+
* Resolve the bot token for a team from the state adapter.
|
|
198
|
+
*/
|
|
199
|
+
private resolveTokenForTeam;
|
|
200
|
+
/**
|
|
201
|
+
* Extract team_id from an interactive payload (form-urlencoded).
|
|
202
|
+
*/
|
|
203
|
+
private extractTeamIdFromInteractive;
|
|
132
204
|
/**
|
|
133
205
|
* Look up user info from Slack API with caching via state adapter.
|
|
134
206
|
* Returns display name and real name, or falls back to user ID.
|
|
135
207
|
*/
|
|
136
208
|
private lookupUser;
|
|
137
209
|
handleWebhook(request: Request, options?: WebhookOptions): Promise<Response>;
|
|
210
|
+
/** Extract and dispatch events from a validated payload */
|
|
211
|
+
private processEventPayload;
|
|
138
212
|
/**
|
|
139
213
|
* Handle Slack interactive payloads (button clicks, view submissions, etc.).
|
|
140
214
|
* These are sent as form-urlencoded with a `payload` JSON field.
|
|
@@ -247,4 +321,4 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
247
321
|
}
|
|
248
322
|
declare function createSlackAdapter(config: SlackAdapterConfig): SlackAdapter;
|
|
249
323
|
|
|
250
|
-
export { SlackAdapter, type SlackAdapterConfig, type SlackEvent, SlackFormatConverter, SlackFormatConverter as SlackMarkdownConverter, type SlackReactionEvent, type SlackThreadId, cardToBlockKit, cardToFallbackText, createSlackAdapter };
|
|
324
|
+
export { type EncryptedTokenData, SlackAdapter, type SlackAdapterConfig, type SlackEvent, SlackFormatConverter, type SlackInstallation, SlackFormatConverter as SlackMarkdownConverter, type SlackReactionEvent, type SlackThreadId, cardToBlockKit, cardToFallbackText, createSlackAdapter, decodeKey };
|