@vercel/slack-bolt 1.3.1 → 1.4.1
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 +84 -8
- package/dist/cli.d.mts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +357 -507
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +385 -497
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +181 -90
- package/dist/index.d.ts +181 -90
- package/dist/index.js +491 -367
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +489 -365
- package/dist/index.mjs.map +1 -1
- package/dist/preview-DAWKwXt1.mjs +689 -0
- package/dist/preview-DAWKwXt1.mjs.map +1 -0
- package/dist/preview-DeqpNNn_.js +794 -0
- package/dist/preview-DeqpNNn_.js.map +1 -0
- package/dist/preview.d.mts +47 -44
- package/dist/preview.d.ts +47 -44
- package/dist/preview.js +3 -13
- package/dist/preview.mjs +2 -6
- package/package.json +9 -8
- package/dist/chunk-B5E7QPZP.mjs +0 -11
- package/dist/chunk-B5E7QPZP.mjs.map +0 -1
- package/dist/chunk-F7WJK2MS.mjs +0 -980
- package/dist/chunk-F7WJK2MS.mjs.map +0 -1
- package/dist/chunk-QHMZVK6J.js +0 -14
- package/dist/chunk-QHMZVK6J.js.map +0 -1
- package/dist/chunk-VI33CCYY.js +0 -1003
- package/dist/chunk-VI33CCYY.js.map +0 -1
- package/dist/preview.js.map +0 -1
- package/dist/preview.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,385 +1,509 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
HANDLER_ERROR: "HandlerError"
|
|
30
|
-
}
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("./preview-DeqpNNn_.js");
|
|
3
|
+
let _slack_bolt = require("@slack/bolt");
|
|
4
|
+
let _slack_logger = require("@slack/logger");
|
|
5
|
+
let _slack_oauth = require("@slack/oauth");
|
|
6
|
+
let _vercel_functions = require("@vercel/functions");
|
|
7
|
+
let node_http = require("node:http");
|
|
8
|
+
let node_net = require("node:net");
|
|
9
|
+
//#region src/errors.ts
|
|
10
|
+
const ERROR_MESSAGES = {
|
|
11
|
+
SIGNING_SECRET_REQUIRED: "SLACK_SIGNING_SECRET is required for VercelReceiver",
|
|
12
|
+
APP_NOT_INITIALIZED: "App not initialized",
|
|
13
|
+
OAUTH_NOT_CONFIGURED: "OAuth is not configured. Provide clientId, clientSecret, and stateSecret to enable OAuth.",
|
|
14
|
+
REQUEST_TIMEOUT: "Request timeout",
|
|
15
|
+
EVENT_NOT_ACKNOWLEDGED: "Event not acknowledged within timeout period",
|
|
16
|
+
MISSING_REQUIRED_HEADER: (header) => `Missing required header: ${header}`,
|
|
17
|
+
REQUEST_VERIFICATION_FAILED: "Request verification failed",
|
|
18
|
+
INTERNAL_SERVER_ERROR: "Internal server error",
|
|
19
|
+
INTERNAL_SERVER_ERROR_HANDLER: "Internal Server Error",
|
|
20
|
+
ACKNOWLEDGMENT_ERROR: "Error in acknowledgment handler",
|
|
21
|
+
CREATE_HANDLER_ERROR: "Error in createHandler:",
|
|
22
|
+
TYPES: {
|
|
23
|
+
VERCEL_RECEIVER_ERROR: "VercelReceiverError",
|
|
24
|
+
SIGNATURE_VERIFICATION_ERROR: "SignatureVerificationError",
|
|
25
|
+
REQUEST_PARSING_ERROR: "RequestParsingError",
|
|
26
|
+
UNEXPECTED_ERROR: "UnexpectedError",
|
|
27
|
+
HANDLER_ERROR: "HandlerError"
|
|
28
|
+
}
|
|
31
29
|
};
|
|
32
30
|
var VercelReceiverError = class extends Error {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
constructor(message, statusCode = 500) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.statusCode = statusCode;
|
|
34
|
+
this.name = ERROR_MESSAGES.TYPES.VERCEL_RECEIVER_ERROR;
|
|
35
|
+
}
|
|
38
36
|
};
|
|
39
37
|
var RequestParsingError = class extends VercelReceiverError {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
constructor(message = "Failed to parse request") {
|
|
39
|
+
super(message, 400);
|
|
40
|
+
this.name = ERROR_MESSAGES.TYPES.REQUEST_PARSING_ERROR;
|
|
41
|
+
}
|
|
44
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* Determines the appropriate HTTP status code for a given error.
|
|
45
|
+
* @param error The error to get status code for
|
|
46
|
+
* @returns HTTP status code
|
|
47
|
+
*/
|
|
45
48
|
function getStatusCode(error) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return 500;
|
|
56
|
-
case "RequestParsingError":
|
|
57
|
-
return 400;
|
|
58
|
-
case "SignatureVerificationError":
|
|
59
|
-
return 400;
|
|
60
|
-
default:
|
|
61
|
-
return 500;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return 500;
|
|
49
|
+
if (error instanceof VercelReceiverError) return error.statusCode;
|
|
50
|
+
if (error && typeof error === "object") switch (error.constructor?.name || error.name) {
|
|
51
|
+
case "ReceiverAuthenticityError": return 401;
|
|
52
|
+
case "ReceiverMultipleAckError": return 500;
|
|
53
|
+
case "RequestParsingError": return 400;
|
|
54
|
+
case "SignatureVerificationError": return 400;
|
|
55
|
+
default: return 500;
|
|
56
|
+
}
|
|
57
|
+
return 500;
|
|
65
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Gets the error message for response.
|
|
61
|
+
* @param error The error to get message for
|
|
62
|
+
* @returns Error message string
|
|
63
|
+
*/
|
|
66
64
|
function getErrorMessage(error) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
return ERROR_MESSAGES.INTERNAL_SERVER_ERROR;
|
|
65
|
+
if (error && typeof error === "object" && "message" in error) return String(error.message);
|
|
66
|
+
return ERROR_MESSAGES.INTERNAL_SERVER_ERROR;
|
|
71
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Gets the error type for response.
|
|
70
|
+
* @param error The error to get type for
|
|
71
|
+
* @returns Error type string
|
|
72
|
+
*/
|
|
72
73
|
function getErrorType(error) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
if (error && typeof error === "object") {
|
|
75
|
+
const ctorName = error.constructor?.name;
|
|
76
|
+
const nameProp = error.name;
|
|
77
|
+
const errorName = ctorName ?? nameProp ?? ERROR_MESSAGES.TYPES.UNEXPECTED_ERROR;
|
|
78
|
+
return errorName === "Error" ? ERROR_MESSAGES.TYPES.UNEXPECTED_ERROR : errorName;
|
|
79
|
+
}
|
|
80
|
+
return ERROR_MESSAGES.TYPES.UNEXPECTED_ERROR;
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/oauth-adapters.ts
|
|
84
|
+
/**
|
|
85
|
+
* Minimal Web <-> Node HTTP adapters for @slack/oauth InstallProvider.
|
|
86
|
+
*
|
|
87
|
+
* These do NOT implement the full IncomingMessage/ServerResponse contract.
|
|
88
|
+
* Only the methods actually called by InstallProvider are intercepted.
|
|
89
|
+
* Do not reuse for other Node libraries without verifying which methods
|
|
90
|
+
* they call.
|
|
91
|
+
*
|
|
92
|
+
* Targets @slack/oauth\@3.0.5 InstallProvider internals.
|
|
93
|
+
* Do not upgrade without running the integration tests.
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* Wraps a Web API Request as a minimal Node.js IncomingMessage
|
|
97
|
+
* so it can be passed to @slack/oauth's InstallProvider methods.
|
|
98
|
+
*/
|
|
99
|
+
function toIncomingMessage(req) {
|
|
100
|
+
const url = new URL(req.url);
|
|
101
|
+
const msg = new node_http.IncomingMessage(new node_net.Socket());
|
|
102
|
+
msg.url = url.pathname + url.search;
|
|
103
|
+
msg.method = req.method;
|
|
104
|
+
for (const [key, value] of req.headers.entries()) msg.headers[key.toLowerCase()] = value;
|
|
105
|
+
if (req.body) req.arrayBuffer().then((buf) => {
|
|
106
|
+
msg.push(Buffer.from(buf));
|
|
107
|
+
msg.push(null);
|
|
108
|
+
}).catch(() => msg.destroy());
|
|
109
|
+
else msg.push(null);
|
|
110
|
+
return msg;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates a fake ServerResponse that captures setHeader/writeHead/end calls
|
|
114
|
+
* and converts them into a Web API Response.
|
|
115
|
+
*
|
|
116
|
+
* InstallProvider uses res.setHeader() for Location, Set-Cookie, Content-Type
|
|
117
|
+
* and res.getHeader() to read back Set-Cookie before appending.
|
|
118
|
+
*/
|
|
119
|
+
function createResponseCapture() {
|
|
120
|
+
let statusCode = 200;
|
|
121
|
+
const capturedHeaders = {};
|
|
122
|
+
const chunks = [];
|
|
123
|
+
const res = new node_http.ServerResponse(new node_http.IncomingMessage(new node_net.Socket()));
|
|
124
|
+
const originalSetHeader = res.setHeader.bind(res);
|
|
125
|
+
res.setHeader = (name, value) => {
|
|
126
|
+
const key = name.toLowerCase();
|
|
127
|
+
if (Array.isArray(value)) capturedHeaders[key] = [...value];
|
|
128
|
+
else capturedHeaders[key] = String(value);
|
|
129
|
+
return originalSetHeader(name, value);
|
|
130
|
+
};
|
|
131
|
+
const originalGetHeader = res.getHeader.bind(res);
|
|
132
|
+
res.getHeader = (name) => {
|
|
133
|
+
const val = capturedHeaders[name.toLowerCase()];
|
|
134
|
+
if (val !== void 0) return val;
|
|
135
|
+
return originalGetHeader(name);
|
|
136
|
+
};
|
|
137
|
+
const originalWriteHead = res.writeHead.bind(res);
|
|
138
|
+
res.writeHead = (code, ...args) => {
|
|
139
|
+
statusCode = code;
|
|
140
|
+
const headersArg = args.find((a) => a !== void 0 && typeof a === "object" && !Array.isArray(a));
|
|
141
|
+
if (headersArg) for (const [key, value] of Object.entries(headersArg)) {
|
|
142
|
+
const lower = key.toLowerCase();
|
|
143
|
+
capturedHeaders[lower] = value;
|
|
144
|
+
}
|
|
145
|
+
return originalWriteHead(code, ...args);
|
|
146
|
+
};
|
|
147
|
+
const originalEnd = res.end.bind(res);
|
|
148
|
+
res.end = (chunk, ...args) => {
|
|
149
|
+
if (chunk !== void 0 && chunk !== null) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
150
|
+
return originalEnd(chunk, ...args);
|
|
151
|
+
};
|
|
152
|
+
res.toResponse = () => {
|
|
153
|
+
const body = chunks.length > 0 ? Buffer.concat(chunks).toString() : null;
|
|
154
|
+
const webHeaders = new Headers();
|
|
155
|
+
for (const [key, value] of Object.entries(capturedHeaders)) if (Array.isArray(value)) for (const v of value) webHeaders.append(key, v);
|
|
156
|
+
else webHeaders.set(key, value);
|
|
157
|
+
return new Response(body, {
|
|
158
|
+
status: statusCode,
|
|
159
|
+
headers: webHeaders
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
return res;
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/index.ts
|
|
166
|
+
const LOG_PREFIX = "[@vercel/slack-bolt]";
|
|
167
|
+
const ACK_TIMEOUT_MS = 3001;
|
|
168
|
+
const SLACK_RETRY_NUM_HEADER = "x-slack-retry-num";
|
|
169
|
+
const SLACK_RETRY_REASON_HEADER = "x-slack-retry-reason";
|
|
170
|
+
const SLACK_TIMESTAMP_HEADER = "x-slack-request-timestamp";
|
|
171
|
+
const SLACK_SIGNATURE_HEADER = "x-slack-signature";
|
|
172
|
+
/**
|
|
173
|
+
* A Slack Bolt receiver implementation designed for Vercel's serverless environment.
|
|
174
|
+
* Handles Slack events, interactions, and slash commands with automatic request verification,
|
|
175
|
+
* background processing, and timeout management.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* import { App } from '@slack/bolt';
|
|
180
|
+
* import { VercelReceiver, createHandler } from '@vercel/slack-bolt';
|
|
181
|
+
*
|
|
182
|
+
* const receiver = new VercelReceiver();
|
|
183
|
+
*
|
|
184
|
+
* const app = new App({
|
|
185
|
+
* receiver,
|
|
186
|
+
* token: process.env.SLACK_BOT_TOKEN,
|
|
187
|
+
* signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
188
|
+
* });
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
*/
|
|
89
192
|
var VercelReceiver = class {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
193
|
+
signingSecret;
|
|
194
|
+
signatureVerification;
|
|
195
|
+
logger;
|
|
196
|
+
customPropertiesExtractor;
|
|
197
|
+
ackTimeoutMs;
|
|
198
|
+
app;
|
|
199
|
+
installer;
|
|
200
|
+
installUrlOptions;
|
|
201
|
+
installCallbackOptions;
|
|
202
|
+
installPathOptions;
|
|
203
|
+
stateVerification;
|
|
204
|
+
/**
|
|
205
|
+
* Gets the logger instance used by this receiver.
|
|
206
|
+
* @returns The logger instance
|
|
207
|
+
*/
|
|
208
|
+
getLogger() {
|
|
209
|
+
return this.logger;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Creates a new VercelReceiver instance.
|
|
213
|
+
*
|
|
214
|
+
* @param options - Configuration options for the receiver
|
|
215
|
+
* @throws {VercelReceiverError} When signing secret is not provided
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* const receiver = new VercelReceiver();
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
constructor({ signingSecret = process.env.SLACK_SIGNING_SECRET, signatureVerification = true, logger, logLevel = _slack_logger.LogLevel.INFO, customPropertiesExtractor, ackTimeoutMs = ACK_TIMEOUT_MS, clientId = process.env.SLACK_CLIENT_ID, clientSecret = process.env.SLACK_CLIENT_SECRET, stateSecret = process.env.SLACK_STATE_SECRET, scopes, redirectUri, installationStore, installerOptions = {} } = {}) {
|
|
223
|
+
if (!signingSecret) throw new VercelReceiverError(ERROR_MESSAGES.SIGNING_SECRET_REQUIRED);
|
|
224
|
+
this.signingSecret = signingSecret;
|
|
225
|
+
this.signatureVerification = signatureVerification;
|
|
226
|
+
this.logger = this.createScopedLogger(logger ?? new _slack_logger.ConsoleLogger(), logLevel);
|
|
227
|
+
this.customPropertiesExtractor = customPropertiesExtractor;
|
|
228
|
+
this.ackTimeoutMs = ackTimeoutMs;
|
|
229
|
+
this.stateVerification = installerOptions.stateVerification;
|
|
230
|
+
if (clientId !== void 0 && clientSecret !== void 0 && (installerOptions.stateVerification === false || stateSecret !== void 0 || installerOptions.stateStore !== void 0)) {
|
|
231
|
+
this.installer = new _slack_oauth.InstallProvider({
|
|
232
|
+
clientId,
|
|
233
|
+
clientSecret,
|
|
234
|
+
stateSecret,
|
|
235
|
+
installationStore,
|
|
236
|
+
logger,
|
|
237
|
+
logLevel,
|
|
238
|
+
directInstall: installerOptions.directInstall,
|
|
239
|
+
stateStore: installerOptions.stateStore,
|
|
240
|
+
stateVerification: installerOptions.stateVerification,
|
|
241
|
+
legacyStateVerification: installerOptions.legacyStateVerification,
|
|
242
|
+
stateCookieName: installerOptions.stateCookieName,
|
|
243
|
+
stateCookieExpirationSeconds: installerOptions.stateCookieExpirationSeconds,
|
|
244
|
+
renderHtmlForInstallPath: installerOptions.renderHtmlForInstallPath,
|
|
245
|
+
authVersion: installerOptions.authVersion ?? "v2",
|
|
246
|
+
clientOptions: installerOptions.clientOptions,
|
|
247
|
+
authorizationUrl: installerOptions.authorizationUrl
|
|
248
|
+
});
|
|
249
|
+
this.installUrlOptions = {
|
|
250
|
+
scopes: scopes ?? [],
|
|
251
|
+
userScopes: installerOptions.userScopes,
|
|
252
|
+
metadata: installerOptions.metadata,
|
|
253
|
+
redirectUri
|
|
254
|
+
};
|
|
255
|
+
this.installCallbackOptions = installerOptions.callbackOptions ?? {};
|
|
256
|
+
this.installPathOptions = installerOptions.installPathOptions ?? {};
|
|
257
|
+
}
|
|
258
|
+
this.logger.debug("VercelReceiver initialized");
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Initializes the receiver with a Slack Bolt app instance.
|
|
262
|
+
* This method is called automatically by the Bolt framework.
|
|
263
|
+
*
|
|
264
|
+
* @param app - The Slack Bolt app instance
|
|
265
|
+
*/
|
|
266
|
+
init(app) {
|
|
267
|
+
this.app = app;
|
|
268
|
+
this.logger.debug("App initialized in VercelReceiver");
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Starts the receiver and returns a handler function for processing requests.
|
|
272
|
+
* This method is called automatically by the Bolt framework.
|
|
273
|
+
*
|
|
274
|
+
* @returns A handler function that processes incoming Slack requests
|
|
275
|
+
*/
|
|
276
|
+
async start() {
|
|
277
|
+
this.logger.debug("VercelReceiver started");
|
|
278
|
+
return this.toHandler();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Stops the receiver. This method is called automatically by the Bolt framework.
|
|
282
|
+
*/
|
|
283
|
+
async stop() {
|
|
284
|
+
this.logger.debug("VercelReceiver stopped");
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Handles requests to the OAuth install path.
|
|
288
|
+
* Renders an "Add to Slack" page or redirects directly to Slack's authorize URL.
|
|
289
|
+
*/
|
|
290
|
+
handleInstall = async (req) => {
|
|
291
|
+
if (!this.installer) throw new VercelReceiverError(ERROR_MESSAGES.OAUTH_NOT_CONFIGURED);
|
|
292
|
+
const nodeReq = toIncomingMessage(req);
|
|
293
|
+
const capture = createResponseCapture();
|
|
294
|
+
await this.installer.handleInstallPath(nodeReq, capture, this.installPathOptions, this.installUrlOptions);
|
|
295
|
+
return capture.toResponse();
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Handles the OAuth redirect callback from Slack.
|
|
299
|
+
* Exchanges the authorization code for tokens and stores the installation.
|
|
300
|
+
*/
|
|
301
|
+
handleCallback = async (req) => {
|
|
302
|
+
if (!this.installer) throw new VercelReceiverError(ERROR_MESSAGES.OAUTH_NOT_CONFIGURED);
|
|
303
|
+
const nodeReq = toIncomingMessage(req);
|
|
304
|
+
const capture = createResponseCapture();
|
|
305
|
+
if (this.stateVerification === false) await this.installer.handleCallback(nodeReq, capture, this.installCallbackOptions, this.installUrlOptions);
|
|
306
|
+
else await this.installer.handleCallback(nodeReq, capture, this.installCallbackOptions);
|
|
307
|
+
return capture.toResponse();
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* Creates a handler function that processes incoming Slack requests.
|
|
311
|
+
* This is the main entry point for handling Slack events in Vercel.
|
|
312
|
+
* It is called automatically by the Bolt framework in the start() method.
|
|
313
|
+
*
|
|
314
|
+
* @returns A handler function compatible with Vercel's function signature
|
|
315
|
+
*/
|
|
316
|
+
toHandler() {
|
|
317
|
+
return async (req) => {
|
|
318
|
+
try {
|
|
319
|
+
const rawBody = await req.text();
|
|
320
|
+
if (this.signatureVerification) this.verifyRequest(req, rawBody);
|
|
321
|
+
const body = await this.parseRequestBody(req, rawBody);
|
|
322
|
+
if (body.type === "url_verification") {
|
|
323
|
+
this.logger.debug("Handling URL verification challenge");
|
|
324
|
+
return Response.json({ challenge: body.challenge });
|
|
325
|
+
}
|
|
326
|
+
return await this.handleSlackEvent(req, body);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
return this.handleError(error);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
async parseRequestBody(req, rawBody) {
|
|
333
|
+
const contentType = req.headers.get("content-type");
|
|
334
|
+
try {
|
|
335
|
+
if (contentType === "application/x-www-form-urlencoded") {
|
|
336
|
+
const parsedBody = {};
|
|
337
|
+
const params = new URLSearchParams(rawBody);
|
|
338
|
+
for (const [key, value] of params.entries()) parsedBody[key] = value;
|
|
339
|
+
if (typeof parsedBody.payload === "string") return JSON.parse(parsedBody.payload);
|
|
340
|
+
return parsedBody;
|
|
341
|
+
}
|
|
342
|
+
if (contentType === "application/json") return JSON.parse(rawBody);
|
|
343
|
+
this.logger.warn(`Unexpected content-type detected: ${contentType}`);
|
|
344
|
+
return JSON.parse(rawBody);
|
|
345
|
+
} catch (e) {
|
|
346
|
+
throw new RequestParsingError(`Failed to parse body as JSON data for content-type: ${contentType}. Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async handleSlackEvent(req, body) {
|
|
350
|
+
if (!this.app) throw new VercelReceiverError(ERROR_MESSAGES.APP_NOT_INITIALIZED, 500);
|
|
351
|
+
let isAcknowledged = false;
|
|
352
|
+
let responseResolver;
|
|
353
|
+
let responseRejecter;
|
|
354
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
355
|
+
responseResolver = resolve;
|
|
356
|
+
responseRejecter = reject;
|
|
357
|
+
});
|
|
358
|
+
const timeoutId = setTimeout(() => {
|
|
359
|
+
if (!isAcknowledged) {
|
|
360
|
+
isAcknowledged = true;
|
|
361
|
+
this.logger.error(ERROR_MESSAGES.EVENT_NOT_ACKNOWLEDGED);
|
|
362
|
+
const error = new VercelReceiverError(ERROR_MESSAGES.REQUEST_TIMEOUT, 408);
|
|
363
|
+
responseRejecter(error);
|
|
364
|
+
}
|
|
365
|
+
}, this.ackTimeoutMs);
|
|
366
|
+
const ackFn = async (responseBody) => {
|
|
367
|
+
this.logger.debug(`ack() call begins (body: ${responseBody})`);
|
|
368
|
+
if (isAcknowledged) throw new _slack_bolt.ReceiverMultipleAckError();
|
|
369
|
+
isAcknowledged = true;
|
|
370
|
+
clearTimeout(timeoutId);
|
|
371
|
+
try {
|
|
372
|
+
let body;
|
|
373
|
+
if (typeof responseBody === "undefined") body = void 0;
|
|
374
|
+
else if (typeof responseBody === "string") body = responseBody;
|
|
375
|
+
else body = JSON.stringify(responseBody);
|
|
376
|
+
const response = new Response(body, {
|
|
377
|
+
status: 200,
|
|
378
|
+
headers: { "Content-Type": "application/json" }
|
|
379
|
+
});
|
|
380
|
+
responseResolver(response);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
this.logger.error(ERROR_MESSAGES.ACKNOWLEDGMENT_ERROR, error);
|
|
383
|
+
responseRejecter(error instanceof Error ? error : new Error(String(error)));
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const event = this.createSlackReceiverEvent({
|
|
387
|
+
body,
|
|
388
|
+
headers: req.headers,
|
|
389
|
+
ack: ackFn,
|
|
390
|
+
request: req
|
|
391
|
+
});
|
|
392
|
+
(0, _vercel_functions.waitUntil)(this.app.processEvent(event).catch((error) => {
|
|
393
|
+
return this.handleError(error);
|
|
394
|
+
}));
|
|
395
|
+
try {
|
|
396
|
+
return await responsePromise;
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return this.handleError(error);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
verifyRequest(req, body) {
|
|
402
|
+
const timestamp = req.headers.get(SLACK_TIMESTAMP_HEADER);
|
|
403
|
+
const signature = req.headers.get(SLACK_SIGNATURE_HEADER);
|
|
404
|
+
if (!signature) throw new _slack_bolt.ReceiverAuthenticityError(ERROR_MESSAGES.MISSING_REQUIRED_HEADER(SLACK_SIGNATURE_HEADER));
|
|
405
|
+
if (!timestamp) throw new _slack_bolt.ReceiverAuthenticityError(ERROR_MESSAGES.MISSING_REQUIRED_HEADER(SLACK_TIMESTAMP_HEADER));
|
|
406
|
+
try {
|
|
407
|
+
(0, _slack_bolt.verifySlackRequest)({
|
|
408
|
+
signingSecret: this.signingSecret,
|
|
409
|
+
body,
|
|
410
|
+
headers: {
|
|
411
|
+
"x-slack-signature": signature,
|
|
412
|
+
"x-slack-request-timestamp": Number.parseInt(timestamp, 10)
|
|
413
|
+
},
|
|
414
|
+
logger: this.logger
|
|
415
|
+
});
|
|
416
|
+
} catch (error) {
|
|
417
|
+
throw new _slack_bolt.ReceiverAuthenticityError(error instanceof Error ? error.message : "Failed to verify request signature");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
createSlackReceiverEvent({ body, headers, ack, request }) {
|
|
421
|
+
const customProperties = this.customPropertiesExtractor ? this.customPropertiesExtractor(request) : {};
|
|
422
|
+
const retryNum = headers.get(SLACK_RETRY_NUM_HEADER) || "0";
|
|
423
|
+
const retryReason = headers.get(SLACK_RETRY_REASON_HEADER) || "";
|
|
424
|
+
return {
|
|
425
|
+
body,
|
|
426
|
+
ack,
|
|
427
|
+
retryNum: Number(retryNum),
|
|
428
|
+
retryReason,
|
|
429
|
+
customProperties
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
handleError(error) {
|
|
433
|
+
const errorMessage = getErrorMessage(error);
|
|
434
|
+
const errorType = getErrorType(error);
|
|
435
|
+
const errorStatusCode = getStatusCode(error);
|
|
436
|
+
this.logger.error(error);
|
|
437
|
+
return new Response(JSON.stringify({
|
|
438
|
+
error: errorMessage,
|
|
439
|
+
type: errorType
|
|
440
|
+
}), {
|
|
441
|
+
status: errorStatusCode,
|
|
442
|
+
headers: { "content-type": "application/json" }
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
createScopedLogger(logger, logLevel) {
|
|
446
|
+
logger.setLevel(logLevel);
|
|
447
|
+
return {
|
|
448
|
+
...logger,
|
|
449
|
+
error: (...args) => logger.error?.(LOG_PREFIX, ...args),
|
|
450
|
+
warn: (...args) => logger.warn?.(LOG_PREFIX, ...args),
|
|
451
|
+
info: (...args) => logger.info?.(LOG_PREFIX, ...args),
|
|
452
|
+
debug: (...args) => logger.debug?.(LOG_PREFIX, ...args),
|
|
453
|
+
setLevel: logger.setLevel,
|
|
454
|
+
getLevel: logger.getLevel
|
|
455
|
+
};
|
|
456
|
+
}
|
|
354
457
|
};
|
|
458
|
+
/**
|
|
459
|
+
* Creates a Vercel-compatible handler function for a Slack Bolt app.
|
|
460
|
+
* This is the recommended way to create handlers for deployment on Vercel.
|
|
461
|
+
*
|
|
462
|
+
* @param {App} app - The initialized Slack Bolt app instance.
|
|
463
|
+
* @param {VercelReceiver} receiver - The VercelReceiver instance.
|
|
464
|
+
* @returns {VercelHandler} A handler function compatible with Vercel's function signature.
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```typescript
|
|
468
|
+
* // api/events.ts
|
|
469
|
+
* import { createHandler } from '@vercel/slack-bolt';
|
|
470
|
+
* import { app, receiver } from '../app';
|
|
471
|
+
*
|
|
472
|
+
* const handler = createHandler(app, receiver);
|
|
473
|
+
*
|
|
474
|
+
* export const POST = async (req: Request) => {
|
|
475
|
+
* return handler(req);
|
|
476
|
+
* };
|
|
477
|
+
* ```
|
|
478
|
+
*
|
|
479
|
+
* @throws {Error} If app initialization fails.
|
|
480
|
+
* @throws {VercelReceiverError} If request processing fails.
|
|
481
|
+
*/
|
|
355
482
|
function createHandler(app, receiver) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
};
|
|
483
|
+
let initPromise = null;
|
|
484
|
+
return async (req) => {
|
|
485
|
+
try {
|
|
486
|
+
if (!initPromise) initPromise = app.init().catch((error) => {
|
|
487
|
+
initPromise = null;
|
|
488
|
+
throw error;
|
|
489
|
+
});
|
|
490
|
+
await initPromise;
|
|
491
|
+
receiver.init(app);
|
|
492
|
+
return (await receiver.start())(req);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.error(ERROR_MESSAGES.CREATE_HANDLER_ERROR, error);
|
|
495
|
+
return new Response(JSON.stringify({
|
|
496
|
+
error: ERROR_MESSAGES.INTERNAL_SERVER_ERROR_HANDLER,
|
|
497
|
+
type: ERROR_MESSAGES.TYPES.HANDLER_ERROR
|
|
498
|
+
}), {
|
|
499
|
+
status: 500,
|
|
500
|
+
headers: { "content-type": "application/json" }
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
};
|
|
380
504
|
}
|
|
381
|
-
|
|
505
|
+
//#endregion
|
|
382
506
|
exports.VercelReceiver = VercelReceiver;
|
|
383
507
|
exports.createHandler = createHandler;
|
|
384
|
-
|
|
508
|
+
|
|
385
509
|
//# sourceMappingURL=index.js.map
|