@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/dist/index.js CHANGED
@@ -1,385 +1,509 @@
1
- 'use strict';
2
-
3
- require('./chunk-QHMZVK6J.js');
4
- var bolt = require('@slack/bolt');
5
- var logger = require('@slack/logger');
6
- var functions = require('@vercel/functions');
7
-
8
- // src/errors.ts
9
- var ERROR_MESSAGES = {
10
- // VercelReceiver errors
11
- SIGNING_SECRET_REQUIRED: "SLACK_SIGNING_SECRET is required for VercelReceiver",
12
- APP_NOT_INITIALIZED: "App not initialized",
13
- REQUEST_TIMEOUT: "Request timeout",
14
- EVENT_NOT_ACKNOWLEDGED: "Event not acknowledged within timeout period",
15
- // Header validation errors
16
- MISSING_REQUIRED_HEADER: (header) => `Missing required header: ${header}`,
17
- // Generic fallback errors
18
- REQUEST_VERIFICATION_FAILED: "Request verification failed",
19
- INTERNAL_SERVER_ERROR: "Internal server error",
20
- INTERNAL_SERVER_ERROR_HANDLER: "Internal Server Error",
21
- ACKNOWLEDGMENT_ERROR: "Error in acknowledgment handler",
22
- CREATE_HANDLER_ERROR: "Error in createHandler:",
23
- // Error type names
24
- TYPES: {
25
- VERCEL_RECEIVER_ERROR: "VercelReceiverError",
26
- SIGNATURE_VERIFICATION_ERROR: "SignatureVerificationError",
27
- REQUEST_PARSING_ERROR: "RequestParsingError",
28
- UNEXPECTED_ERROR: "UnexpectedError",
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
- constructor(message, statusCode = 500) {
34
- super(message);
35
- this.statusCode = statusCode;
36
- this.name = ERROR_MESSAGES.TYPES.VERCEL_RECEIVER_ERROR;
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
- constructor(message = "Failed to parse request") {
41
- super(message, 400);
42
- this.name = ERROR_MESSAGES.TYPES.REQUEST_PARSING_ERROR;
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
- if (error instanceof VercelReceiverError) {
47
- return error.statusCode;
48
- }
49
- if (error && typeof error === "object") {
50
- const errorName = error.constructor?.name || error.name;
51
- switch (errorName) {
52
- case "ReceiverAuthenticityError":
53
- return 401;
54
- case "ReceiverMultipleAckError":
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
- if (error && typeof error === "object" && "message" in error) {
68
- return String(error.message);
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
- if (error && typeof error === "object") {
74
- const ctorName = error.constructor?.name;
75
- const nameProp = error.name;
76
- const errorName = ctorName ?? nameProp ?? ERROR_MESSAGES.TYPES.UNEXPECTED_ERROR;
77
- return errorName === "Error" ? ERROR_MESSAGES.TYPES.UNEXPECTED_ERROR : errorName;
78
- }
79
- return ERROR_MESSAGES.TYPES.UNEXPECTED_ERROR;
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
- // src/index.ts
83
- var LOG_PREFIX = "[@vercel/slack-bolt]";
84
- var ACK_TIMEOUT_MS = 3001;
85
- var SLACK_RETRY_NUM_HEADER = "x-slack-retry-num";
86
- var SLACK_RETRY_REASON_HEADER = "x-slack-retry-reason";
87
- var SLACK_TIMESTAMP_HEADER = "x-slack-request-timestamp";
88
- var SLACK_SIGNATURE_HEADER = "x-slack-signature";
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
- signingSecret;
91
- signatureVerification;
92
- logger;
93
- customPropertiesExtractor;
94
- ackTimeoutMs;
95
- app;
96
- /**
97
- * Gets the logger instance used by this receiver.
98
- * @returns The logger instance
99
- */
100
- getLogger() {
101
- return this.logger;
102
- }
103
- /**
104
- * Creates a new VercelReceiver instance.
105
- *
106
- * @param options - Configuration options for the receiver
107
- * @throws {VercelReceiverError} When signing secret is not provided
108
- *
109
- * @example
110
- * ```typescript
111
- * const receiver = new VercelReceiver();
112
- * ```
113
- */
114
- constructor({
115
- signingSecret = process.env.SLACK_SIGNING_SECRET,
116
- signatureVerification = true,
117
- logger: logger$1,
118
- logLevel = logger.LogLevel.INFO,
119
- customPropertiesExtractor,
120
- ackTimeoutMs = ACK_TIMEOUT_MS
121
- } = {}) {
122
- if (!signingSecret) {
123
- throw new VercelReceiverError(ERROR_MESSAGES.SIGNING_SECRET_REQUIRED);
124
- }
125
- this.signingSecret = signingSecret;
126
- this.signatureVerification = signatureVerification;
127
- this.logger = this.createScopedLogger(
128
- logger$1 ?? new logger.ConsoleLogger(),
129
- logLevel
130
- );
131
- this.customPropertiesExtractor = customPropertiesExtractor;
132
- this.ackTimeoutMs = ackTimeoutMs;
133
- this.logger.debug("VercelReceiver initialized");
134
- }
135
- /**
136
- * Initializes the receiver with a Slack Bolt app instance.
137
- * This method is called automatically by the Bolt framework.
138
- *
139
- * @param app - The Slack Bolt app instance
140
- */
141
- init(app) {
142
- this.app = app;
143
- this.logger.debug("App initialized in VercelReceiver");
144
- }
145
- /**
146
- * Starts the receiver and returns a handler function for processing requests.
147
- * This method is called automatically by the Bolt framework.
148
- *
149
- * @returns A handler function that processes incoming Slack requests
150
- */
151
- async start() {
152
- this.logger.debug("VercelReceiver started");
153
- return this.toHandler();
154
- }
155
- /**
156
- * Stops the receiver. This method is called automatically by the Bolt framework.
157
- */
158
- async stop() {
159
- this.logger.debug("VercelReceiver stopped");
160
- }
161
- /**
162
- * Creates a handler function that processes incoming Slack requests.
163
- * This is the main entry point for handling Slack events in Vercel.
164
- * It is called automatically by the Bolt framework in the start() method.
165
- *
166
- * @returns A handler function compatible with Vercel's function signature
167
- */
168
- toHandler() {
169
- return async (req) => {
170
- try {
171
- const rawBody = await req.text();
172
- if (this.signatureVerification) {
173
- this.verifyRequest(req, rawBody);
174
- }
175
- const body = await this.parseRequestBody(req, rawBody);
176
- if (body.type === "url_verification") {
177
- this.logger.debug("Handling URL verification challenge");
178
- return Response.json({ challenge: body.challenge });
179
- }
180
- return await this.handleSlackEvent(req, body);
181
- } catch (error) {
182
- return this.handleError(error);
183
- }
184
- };
185
- }
186
- async parseRequestBody(req, rawBody) {
187
- const contentType = req.headers.get("content-type");
188
- try {
189
- if (contentType === "application/x-www-form-urlencoded") {
190
- const parsedBody = {};
191
- const params = new URLSearchParams(rawBody);
192
- for (const [key, value] of params.entries()) {
193
- parsedBody[key] = value;
194
- }
195
- if (typeof parsedBody.payload === "string") {
196
- return JSON.parse(parsedBody.payload);
197
- }
198
- return parsedBody;
199
- }
200
- if (contentType === "application/json") {
201
- return JSON.parse(rawBody);
202
- }
203
- this.logger.warn(`Unexpected content-type detected: ${contentType}`);
204
- return JSON.parse(rawBody);
205
- } catch (e) {
206
- throw new RequestParsingError(
207
- `Failed to parse body as JSON data for content-type: ${contentType}. Error: ${e instanceof Error ? e.message : String(e)}`
208
- );
209
- }
210
- }
211
- async handleSlackEvent(req, body) {
212
- if (!this.app) {
213
- throw new VercelReceiverError(ERROR_MESSAGES.APP_NOT_INITIALIZED, 500);
214
- }
215
- let isAcknowledged = false;
216
- let responseResolver;
217
- let responseRejecter;
218
- const responsePromise = new Promise((resolve, reject) => {
219
- responseResolver = resolve;
220
- responseRejecter = reject;
221
- });
222
- const timeoutId = setTimeout(() => {
223
- if (!isAcknowledged) {
224
- isAcknowledged = true;
225
- this.logger.error(ERROR_MESSAGES.EVENT_NOT_ACKNOWLEDGED);
226
- const error = new VercelReceiverError(
227
- ERROR_MESSAGES.REQUEST_TIMEOUT,
228
- 408
229
- );
230
- responseRejecter(error);
231
- }
232
- }, this.ackTimeoutMs);
233
- const ackFn = async (responseBody) => {
234
- this.logger.debug(`ack() call begins (body: ${responseBody})`);
235
- if (isAcknowledged) {
236
- throw new bolt.ReceiverMultipleAckError();
237
- }
238
- isAcknowledged = true;
239
- clearTimeout(timeoutId);
240
- try {
241
- let body2;
242
- if (typeof responseBody === "undefined") {
243
- body2 = void 0;
244
- } else if (typeof responseBody === "string") {
245
- body2 = responseBody;
246
- } else {
247
- body2 = JSON.stringify(responseBody);
248
- }
249
- const response = new Response(body2, {
250
- status: 200,
251
- headers: {
252
- "Content-Type": "application/json"
253
- }
254
- });
255
- responseResolver(response);
256
- } catch (error) {
257
- this.logger.error(ERROR_MESSAGES.ACKNOWLEDGMENT_ERROR, error);
258
- responseRejecter(
259
- error instanceof Error ? error : new Error(String(error))
260
- );
261
- }
262
- };
263
- const event = this.createSlackReceiverEvent({
264
- body,
265
- headers: req.headers,
266
- ack: ackFn,
267
- request: req
268
- });
269
- functions.waitUntil(
270
- this.app.processEvent(event).catch((error) => {
271
- return this.handleError(error);
272
- })
273
- );
274
- try {
275
- return await responsePromise;
276
- } catch (error) {
277
- return this.handleError(error);
278
- }
279
- }
280
- verifyRequest(req, body) {
281
- const timestamp = req.headers.get(SLACK_TIMESTAMP_HEADER);
282
- const signature = req.headers.get(SLACK_SIGNATURE_HEADER);
283
- if (!signature) {
284
- throw new bolt.ReceiverAuthenticityError(
285
- ERROR_MESSAGES.MISSING_REQUIRED_HEADER(SLACK_SIGNATURE_HEADER)
286
- );
287
- }
288
- if (!timestamp) {
289
- throw new bolt.ReceiverAuthenticityError(
290
- ERROR_MESSAGES.MISSING_REQUIRED_HEADER(SLACK_TIMESTAMP_HEADER)
291
- );
292
- }
293
- try {
294
- bolt.verifySlackRequest({
295
- signingSecret: this.signingSecret,
296
- body,
297
- headers: {
298
- "x-slack-signature": signature,
299
- "x-slack-request-timestamp": Number.parseInt(timestamp, 10)
300
- },
301
- logger: this.logger
302
- });
303
- } catch (error) {
304
- throw new bolt.ReceiverAuthenticityError(
305
- error instanceof Error ? error.message : "Failed to verify request signature"
306
- );
307
- }
308
- }
309
- createSlackReceiverEvent({
310
- body,
311
- headers,
312
- ack,
313
- request
314
- }) {
315
- const customProperties = this.customPropertiesExtractor ? this.customPropertiesExtractor(request) : {};
316
- const retryNum = headers.get(SLACK_RETRY_NUM_HEADER) || "0";
317
- const retryReason = headers.get(SLACK_RETRY_REASON_HEADER) || "";
318
- return {
319
- body,
320
- ack,
321
- retryNum: Number(retryNum),
322
- retryReason,
323
- customProperties
324
- };
325
- }
326
- handleError(error) {
327
- const errorMessage = getErrorMessage(error);
328
- const errorType = getErrorType(error);
329
- const errorStatusCode = getStatusCode(error);
330
- this.logger.error(error);
331
- return new Response(
332
- JSON.stringify({
333
- error: errorMessage,
334
- type: errorType
335
- }),
336
- {
337
- status: errorStatusCode,
338
- headers: { "content-type": "application/json" }
339
- }
340
- );
341
- }
342
- createScopedLogger(logger, logLevel) {
343
- logger.setLevel(logLevel);
344
- return {
345
- ...logger,
346
- error: (...args) => logger.error?.(LOG_PREFIX, ...args),
347
- warn: (...args) => logger.warn?.(LOG_PREFIX, ...args),
348
- info: (...args) => logger.info?.(LOG_PREFIX, ...args),
349
- debug: (...args) => logger.debug?.(LOG_PREFIX, ...args),
350
- setLevel: logger.setLevel,
351
- getLevel: logger.getLevel
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
- let initPromise = null;
357
- return async (req) => {
358
- try {
359
- if (!initPromise) {
360
- initPromise = app.init().catch((error) => {
361
- initPromise = null;
362
- throw error;
363
- });
364
- }
365
- await initPromise;
366
- receiver.init(app);
367
- const handler = await receiver.start();
368
- return handler(req);
369
- } catch (error) {
370
- console.error(ERROR_MESSAGES.CREATE_HANDLER_ERROR, error);
371
- return new Response(
372
- JSON.stringify({
373
- error: ERROR_MESSAGES.INTERNAL_SERVER_ERROR_HANDLER,
374
- type: ERROR_MESSAGES.TYPES.HANDLER_ERROR
375
- }),
376
- { status: 500, headers: { "content-type": "application/json" } }
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
- //# sourceMappingURL=index.js.map
508
+
385
509
  //# sourceMappingURL=index.js.map