slack.ts 0.0.8 → 0.0.10
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/src/client.d.ts +7 -1
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +8 -0
- package/dist/src/receivers/fetch.d.ts +52 -0
- package/dist/src/receivers/fetch.d.ts.map +1 -0
- package/dist/src/receivers/fetch.js +92 -0
- package/dist/src/receivers/http.d.ts +38 -0
- package/dist/src/receivers/http.d.ts.map +1 -0
- package/dist/src/receivers/http.js +110 -0
- package/dist/src/receivers/socket.d.ts.map +1 -1
- package/dist/src/receivers/socket.js +22 -17
- package/dist/src/utils/http.d.ts +31 -0
- package/dist/src/utils/http.d.ts.map +1 -0
- package/dist/src/utils/http.js +105 -0
- package/package.json +1 -1
package/dist/src/client.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import type { BlockAction, BlockActionMap, BlockActions, BlockActionTypes } from
|
|
|
6
6
|
import type { ViewSubmission } from "./api/interactive/view_submission.js";
|
|
7
7
|
import type { AnyMessage, NormalMessage } from "./api/types/message.js";
|
|
8
8
|
import { type SocketEventsReceiverOptions } from "./receivers/socket.js";
|
|
9
|
+
import { type HttpServerReceiverOptions } from "./receivers/http.js";
|
|
10
|
+
import { type HttpFetchReceiverOptions } from "./receivers/fetch.js";
|
|
9
11
|
import { Action, type ActionInstance } from "./resources/action.js";
|
|
10
12
|
import { Channel, ChannelRef } from "./resources/channel.js";
|
|
11
13
|
import { type MessageInstance } from "./resources/message.js";
|
|
@@ -14,7 +16,11 @@ import { type SlashCommandInstance } from "./resources/slash.js";
|
|
|
14
16
|
import type { IM, MPIM, PrivateChannel, PublicChannel } from "./api/types/conversation.js";
|
|
15
17
|
type ReceiverOptions = ({
|
|
16
18
|
type: "socket";
|
|
17
|
-
} & DistributiveOmit<SocketEventsReceiverOptions, "client">) | {
|
|
19
|
+
} & DistributiveOmit<SocketEventsReceiverOptions, "client">) | ({
|
|
20
|
+
type: "http";
|
|
21
|
+
} & DistributiveOmit<HttpServerReceiverOptions, "client">) | ({
|
|
22
|
+
type: "fetch";
|
|
23
|
+
} & DistributiveOmit<HttpFetchReceiverOptions, "client">) | {
|
|
18
24
|
type: "dummy";
|
|
19
25
|
options?: never;
|
|
20
26
|
};
|
package/dist/src/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,QAAQ,CAAA;AACjC,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACzF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EACX,WAAW,EACX,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,MAAM,iCAAiC,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGpE,OAAO,EAAwB,KAAK,2BAA2B,EAAE,MAAM,oBAAoB,CAAA;AAC3F,OAAO,EAAE,MAAM,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAW,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGtD,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,KAAK,EAEX,EAAE,EACF,IAAI,EACJ,cAAc,EACd,aAAa,EACb,MAAM,0BAA0B,CAAA;AAGjC,KAAK,eAAe,GACjB,CAAC;IACD,IAAI,EAAE,QAAQ,CAAA;CACb,GAAG,gBAAgB,CAAC,2BAA2B,EAAE,QAAQ,CAAC,CAAC,GAC5D;IACA,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,CAAC,EAAE,KAAK,CAAA;CACd,CAAA;AAEJ,UAAU,UAAU;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAAG;IACjC,OAAO,EAAE,eAAe,CAAA;IACxB,MAAM,EAAE,GAAG,CAAA;IACX,KAAK,EAAE,YAAY,CAAC,YAAY,CAAC,CAAA;CACjC,CAAA;AACD,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,mBAAmB,KAAK,OAAO,CAAA;AAEpE,MAAM,MAAM,iBAAiB,CAAC,KAAK,SAAS,SAAS,IAAI;IACxD,MAAM,EAAE,GAAG,CAAA;IACX,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,CAAA;CAC1B,CAAA;AACD,MAAM,MAAM,aAAa,CAAC,KAAK,SAAS,SAAS,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC,KAAK,OAAO,CAAA;AAEhG,MAAM,MAAM,mBAAmB,CAAC,IAAI,SAAS,WAAW,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,OAAO,CAAA;AAE3F,KAAK,cAAc,GAAG;IACrB,cAAc,EAAE,aAAa,CAAA;IAC7B,eAAe,EAAE,cAAc,CAAA;IAC/B,IAAI,EAAE,IAAI,CAAA;IACV,EAAE,EAAE,EAAE,CAAA;CACN,CAAA;AAED,qBAAa,GAAI,SAAQ,YAAY,CAAC,WAAW,CAAC;;gBAIrC,EAAE,KAAK,EAAE,QAA4B,EAAE,GAAE,UAAe;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/src/client.js
CHANGED
|
@@ -3,6 +3,8 @@ import { POST_METHODS, } from "./api/index.js";
|
|
|
3
3
|
import { SlackWebAPIError, SlackWebAPIPlatformError } from "./error.js";
|
|
4
4
|
import { DummyReceiver } from "./receivers/dummy.js";
|
|
5
5
|
import { SocketEventsReceiver } from "./receivers/socket.js";
|
|
6
|
+
import { HttpServerReceiver } from "./receivers/http.js";
|
|
7
|
+
import { HttpFetchReceiver } from "./receivers/fetch.js";
|
|
6
8
|
import { Action } from "./resources/action.js";
|
|
7
9
|
import { Channel, ChannelRef } from "./resources/channel.js";
|
|
8
10
|
import { Message } from "./resources/message.js";
|
|
@@ -20,6 +22,12 @@ export class App extends EventEmitter {
|
|
|
20
22
|
case "socket":
|
|
21
23
|
this.#receiver = new SocketEventsReceiver({ ...receiver, client: this });
|
|
22
24
|
break;
|
|
25
|
+
case "http":
|
|
26
|
+
this.#receiver = new HttpServerReceiver({ ...receiver, client: this });
|
|
27
|
+
break;
|
|
28
|
+
case "fetch":
|
|
29
|
+
this.#receiver = new HttpFetchReceiver({ ...receiver, client: this });
|
|
30
|
+
break;
|
|
23
31
|
default:
|
|
24
32
|
this.#receiver = new DummyReceiver();
|
|
25
33
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import type { App } from "../client.js";
|
|
3
|
+
import type { EventsReceiver, ReceiverEventMap } from "./base.js";
|
|
4
|
+
export interface HttpFetchReceiverOptions {
|
|
5
|
+
signingSecret: string;
|
|
6
|
+
client: App;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Stateless HTTP receiver that exposes a fetch() method.
|
|
10
|
+
*
|
|
11
|
+
* This receiver is designed for serverless and other environments where the user manages their HTTP
|
|
12
|
+
* server. It handles a single request at a time without maintaining server state.
|
|
13
|
+
*
|
|
14
|
+
* Example usage:
|
|
15
|
+
*
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const receiver = new HttpFetchReceiver({
|
|
18
|
+
* signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
19
|
+
* client: app,
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* // For Cloudflare Workers, Deno, etc.:
|
|
23
|
+
* export default {
|
|
24
|
+
* fetch: (request) => receiver.fetch(request),
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* // For Express:
|
|
28
|
+
* app.post('/slack/events', async (req, res) => {
|
|
29
|
+
* const response = await receiver.fetch(
|
|
30
|
+
* new Request('http://localhost/', {
|
|
31
|
+
* method: req.method,
|
|
32
|
+
* headers: req.headers,
|
|
33
|
+
* body: req.body,
|
|
34
|
+
* }),
|
|
35
|
+
* )
|
|
36
|
+
* res.status(response.status).send(await response.text())
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare class HttpFetchReceiver extends EventEmitter<ReceiverEventMap> implements EventsReceiver {
|
|
41
|
+
#private;
|
|
42
|
+
constructor({ signingSecret, client }: HttpFetchReceiverOptions);
|
|
43
|
+
/**
|
|
44
|
+
* Handles an incoming HTTP request from Slack.
|
|
45
|
+
*
|
|
46
|
+
* @param request The incoming HTTP request
|
|
47
|
+
* @returns An HTTP response to send back to Slack
|
|
48
|
+
*/
|
|
49
|
+
fetch(request: Request): Promise<Response>;
|
|
50
|
+
start(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/receivers/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAEpC,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAA;AAE9D,MAAM,WAAW,wBAAwB;IACxC,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,GAAG,CAAA;CACX;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,iBAAkB,SAAQ,YAAY,CAAC,gBAAgB,CAAE,YAAW,cAAc;;gBAIlF,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,wBAAwB;IAY/D;;;;;OAKG;IACG,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAgD1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAC5B"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { parseSlackRequest } from "../utils/http.js";
|
|
3
|
+
/**
|
|
4
|
+
* Stateless HTTP receiver that exposes a fetch() method.
|
|
5
|
+
*
|
|
6
|
+
* This receiver is designed for serverless and other environments where the user manages their HTTP
|
|
7
|
+
* server. It handles a single request at a time without maintaining server state.
|
|
8
|
+
*
|
|
9
|
+
* Example usage:
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const receiver = new HttpFetchReceiver({
|
|
13
|
+
* signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
14
|
+
* client: app,
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* // For Cloudflare Workers, Deno, etc.:
|
|
18
|
+
* export default {
|
|
19
|
+
* fetch: (request) => receiver.fetch(request),
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* // For Express:
|
|
23
|
+
* app.post('/slack/events', async (req, res) => {
|
|
24
|
+
* const response = await receiver.fetch(
|
|
25
|
+
* new Request('http://localhost/', {
|
|
26
|
+
* method: req.method,
|
|
27
|
+
* headers: req.headers,
|
|
28
|
+
* body: req.body,
|
|
29
|
+
* }),
|
|
30
|
+
* )
|
|
31
|
+
* res.status(response.status).send(await response.text())
|
|
32
|
+
* })
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export class HttpFetchReceiver extends EventEmitter {
|
|
36
|
+
#signingSecret;
|
|
37
|
+
#client;
|
|
38
|
+
constructor({ signingSecret, client }) {
|
|
39
|
+
super({ captureRejections: true });
|
|
40
|
+
this.on("error", this.#onError.bind(this));
|
|
41
|
+
this.#signingSecret = signingSecret;
|
|
42
|
+
this.#client = client;
|
|
43
|
+
}
|
|
44
|
+
#onError(error) {
|
|
45
|
+
console.error("[http-fetch] error occurred handling event");
|
|
46
|
+
console.error(error);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Handles an incoming HTTP request from Slack.
|
|
50
|
+
*
|
|
51
|
+
* @param request The incoming HTTP request
|
|
52
|
+
* @returns An HTTP response to send back to Slack
|
|
53
|
+
*/
|
|
54
|
+
async fetch(request) {
|
|
55
|
+
try {
|
|
56
|
+
const payload = await parseSlackRequest(request, this.#signingSecret);
|
|
57
|
+
return this.#handlePayload(payload);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error("[http-fetch] error parsing request:", error instanceof Error ? error.message : error);
|
|
61
|
+
if (error instanceof Error && error.message.includes("Invalid signature")) {
|
|
62
|
+
return new Response("Unauthorized", { status: 403 });
|
|
63
|
+
}
|
|
64
|
+
return new Response("Bad Request", { status: 400 });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
#handlePayload(payload) {
|
|
68
|
+
switch (payload.type) {
|
|
69
|
+
case "url_verification":
|
|
70
|
+
return new Response(JSON.stringify({ challenge: payload.challenge }), {
|
|
71
|
+
status: 200,
|
|
72
|
+
headers: { "Content-Type": "application/json" },
|
|
73
|
+
});
|
|
74
|
+
case "event":
|
|
75
|
+
this.emit("event", payload.payload);
|
|
76
|
+
return new Response(null, { status: 200 });
|
|
77
|
+
case "block_actions":
|
|
78
|
+
this.emit("block_actions", payload.payload);
|
|
79
|
+
return new Response(null, { status: 200 });
|
|
80
|
+
case "view_submission":
|
|
81
|
+
this.emit("view_submission", payload.payload);
|
|
82
|
+
return new Response(null, { status: 200 });
|
|
83
|
+
case "slash_command":
|
|
84
|
+
this.emit("slash_command", payload.payload);
|
|
85
|
+
return new Response(null, { status: 200 });
|
|
86
|
+
default:
|
|
87
|
+
const _exhaustive = payload;
|
|
88
|
+
return new Response(null, { status: 200 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async start() { }
|
|
92
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import type { EventsReceiver, ReceiverEventMap } from "./base.js";
|
|
3
|
+
import { type HttpFetchReceiverOptions } from "./fetch.js";
|
|
4
|
+
export interface HttpServerReceiverOptions extends Omit<HttpFetchReceiverOptions, "signingSecret"> {
|
|
5
|
+
signingSecret: string;
|
|
6
|
+
port?: number;
|
|
7
|
+
path?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* HTTP server receiver using Node.js built-in http module.
|
|
11
|
+
*
|
|
12
|
+
* This receiver starts its own HTTP server and listens for Slack events on a specified port and
|
|
13
|
+
* path.
|
|
14
|
+
*
|
|
15
|
+
* Example usage:
|
|
16
|
+
*
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const app = new App({
|
|
19
|
+
* token: process.env.SLACK_BOT_TOKEN,
|
|
20
|
+
* receiver: {
|
|
21
|
+
* type: 'http',
|
|
22
|
+
* signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
23
|
+
* port: 3000,
|
|
24
|
+
* path: '/slack/events',
|
|
25
|
+
* },
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* await app.start()
|
|
29
|
+
* console.log('Server listening on port 3000 at /slack/events')
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class HttpServerReceiver extends EventEmitter<ReceiverEventMap> implements EventsReceiver {
|
|
33
|
+
#private;
|
|
34
|
+
constructor({ signingSecret, client, port, path, }: HttpServerReceiverOptions);
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
stop(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/receivers/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAA;AAC9D,OAAO,EAAqB,KAAK,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAE1E,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,wBAAwB,EAAE,eAAe,CAAC;IACjG,aAAa,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,kBAAmB,SAAQ,YAAY,CAAC,gBAAgB,CAAE,YAAW,cAAc;;gBAMnF,EACX,aAAa,EACb,MAAM,EACN,IAAW,EACX,IAAsB,GACtB,EAAE,yBAAyB;IAmBtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAatB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAuD3B"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { createServer } from "http";
|
|
3
|
+
import { HttpFetchReceiver } from "./fetch.js";
|
|
4
|
+
/**
|
|
5
|
+
* HTTP server receiver using Node.js built-in http module.
|
|
6
|
+
*
|
|
7
|
+
* This receiver starts its own HTTP server and listens for Slack events on a specified port and
|
|
8
|
+
* path.
|
|
9
|
+
*
|
|
10
|
+
* Example usage:
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const app = new App({
|
|
14
|
+
* token: process.env.SLACK_BOT_TOKEN,
|
|
15
|
+
* receiver: {
|
|
16
|
+
* type: 'http',
|
|
17
|
+
* signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
18
|
+
* port: 3000,
|
|
19
|
+
* path: '/slack/events',
|
|
20
|
+
* },
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* await app.start()
|
|
24
|
+
* console.log('Server listening on port 3000 at /slack/events')
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class HttpServerReceiver extends EventEmitter {
|
|
28
|
+
#fetchReceiver;
|
|
29
|
+
#server;
|
|
30
|
+
#port;
|
|
31
|
+
#path;
|
|
32
|
+
constructor({ signingSecret, client, port = 3000, path = "/slack/events", }) {
|
|
33
|
+
super({ captureRejections: true });
|
|
34
|
+
this.on("error", this.#onError.bind(this));
|
|
35
|
+
this.#fetchReceiver = new HttpFetchReceiver({ signingSecret, client });
|
|
36
|
+
this.#port = port;
|
|
37
|
+
this.#path = path;
|
|
38
|
+
this.#fetchReceiver.on("event", (payload) => this.emit("event", payload));
|
|
39
|
+
this.#fetchReceiver.on("block_actions", (payload) => this.emit("block_actions", payload));
|
|
40
|
+
this.#fetchReceiver.on("view_submission", (payload) => this.emit("view_submission", payload));
|
|
41
|
+
this.#fetchReceiver.on("slash_command", (payload) => this.emit("slash_command", payload));
|
|
42
|
+
}
|
|
43
|
+
#onError(error) {
|
|
44
|
+
console.error("[http-server] error occurred");
|
|
45
|
+
console.error(error);
|
|
46
|
+
}
|
|
47
|
+
async start() {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
this.#server = createServer(this.#onRequest.bind(this));
|
|
50
|
+
this.#server.on("error", reject);
|
|
51
|
+
this.#server.listen(this.#port, () => {
|
|
52
|
+
console.debug(`[http-server] listening on port ${this.#port} at ${this.#path}`);
|
|
53
|
+
resolve();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async stop() {
|
|
58
|
+
if (!this.#server)
|
|
59
|
+
return;
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
this.#server.close((err) => {
|
|
62
|
+
if (err)
|
|
63
|
+
reject(err);
|
|
64
|
+
else
|
|
65
|
+
resolve();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async #onRequest(req, res) {
|
|
70
|
+
if (req.method !== "POST" || req.url !== this.#path) {
|
|
71
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
72
|
+
res.end("Not Found");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const headers = new Headers();
|
|
77
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
78
|
+
if (typeof value === "string") {
|
|
79
|
+
headers.set(key, value);
|
|
80
|
+
}
|
|
81
|
+
else if (Array.isArray(value)) {
|
|
82
|
+
headers.set(key, value.join(", "));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const chunks = [];
|
|
86
|
+
for await (const chunk of req) {
|
|
87
|
+
chunks.push(chunk);
|
|
88
|
+
}
|
|
89
|
+
const body = Buffer.concat(chunks);
|
|
90
|
+
const webRequest = new Request("http://localhost/", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers,
|
|
93
|
+
body: body.length > 0 ? body : undefined,
|
|
94
|
+
});
|
|
95
|
+
const response = await this.#fetchReceiver.fetch(webRequest);
|
|
96
|
+
res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
|
|
97
|
+
if (response.body) {
|
|
98
|
+
res.end(await response.text());
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
res.end();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("[http-server] error handling request:", error);
|
|
106
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
107
|
+
res.end("Internal Server Error");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../../src/receivers/socket.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,QAAQ,CAAA;AAIjC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAA;AAG9D,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,GAAG,CAAA;CACX;AAED,qBAAa,oBAAqB,SAAQ,YAAY,CAAC,gBAAgB,CAAE,YAAW,cAAc;;IAE1F,MAAM,EAAE,GAAG,CAAA;gBAGN,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,2BAA2B;IAYvD,KAAK;YAIG,QAAQ;
|
|
1
|
+
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../../src/receivers/socket.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,QAAQ,CAAA;AAIjC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAA;AAG9D,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,GAAG,CAAA;CACX;AAED,qBAAa,oBAAqB,SAAQ,YAAY,CAAC,gBAAgB,CAAE,YAAW,cAAc;;IAE1F,MAAM,EAAE,GAAG,CAAA;gBAGN,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,2BAA2B;IAYvD,KAAK;YAIG,QAAQ;CAoDtB"}
|
|
@@ -31,24 +31,29 @@ export class SocketEventsReceiver extends EventEmitter {
|
|
|
31
31
|
#onMessage(event) {
|
|
32
32
|
console.debug('[socket-mode] message received');
|
|
33
33
|
if (typeof event.data === 'string') {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
try {
|
|
35
|
+
const data = JSON.parse(event.data);
|
|
36
|
+
if (data.type === 'events_api') {
|
|
37
|
+
this.#ws?.send(JSON.stringify({ envelope_id: data.envelope_id }));
|
|
38
|
+
this.emit('event', data.payload);
|
|
39
|
+
}
|
|
40
|
+
else if (data.type === 'interactive') {
|
|
41
|
+
this.#ws?.send(JSON.stringify({ envelope_id: data.envelope_id }));
|
|
42
|
+
this.emit(data.payload.type, data.payload);
|
|
43
|
+
}
|
|
44
|
+
else if (data.type === 'slash_commands') {
|
|
45
|
+
this.#ws?.send(JSON.stringify({ envelope_id: data.envelope_id }));
|
|
46
|
+
this.emit('slash_command', data.payload);
|
|
47
|
+
}
|
|
48
|
+
else if (data.type === 'hello') {
|
|
49
|
+
console.debug('[socket-mode] received server hello, app id', data.connection_info.app_id);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.warn('[socket-mode] unknown message:', data);
|
|
53
|
+
}
|
|
38
54
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.emit(data.payload.type, data.payload);
|
|
42
|
-
}
|
|
43
|
-
else if (data.type === 'slash_commands') {
|
|
44
|
-
this.#ws?.send(JSON.stringify({ envelope_id: data.envelope_id }));
|
|
45
|
-
this.emit('slash_command', data.payload);
|
|
46
|
-
}
|
|
47
|
-
else if (data.type === 'hello') {
|
|
48
|
-
console.debug('[socket-mode] received server hello, app id', data.connection_info.app_id);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
console.warn('[socket-mode] unknown message:', data);
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error('[socket-mode] failed to parse message:', error instanceof Error ? error.message : error);
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AllEvents, EventWrapper } from "../api/events/index.js";
|
|
2
|
+
import type { BlockActions } from "../api/interactive/block_actions.js";
|
|
3
|
+
import type { ViewSubmission } from "../api/interactive/view_submission.js";
|
|
4
|
+
import type { SlashCommandPayload } from "../api/slash/index.js";
|
|
5
|
+
export type SlackHttpPayload = {
|
|
6
|
+
type: "url_verification";
|
|
7
|
+
challenge: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: "event";
|
|
10
|
+
payload: EventWrapper<AllEvents>;
|
|
11
|
+
} | {
|
|
12
|
+
type: "block_actions";
|
|
13
|
+
payload: BlockActions;
|
|
14
|
+
} | {
|
|
15
|
+
type: "view_submission";
|
|
16
|
+
payload: ViewSubmission;
|
|
17
|
+
} | {
|
|
18
|
+
type: "slash_command";
|
|
19
|
+
payload: SlashCommandPayload;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Parses and validates a Slack HTTP request.
|
|
23
|
+
*
|
|
24
|
+
* @param request The incoming request
|
|
25
|
+
* @param signingSecret The Slack app signing secret
|
|
26
|
+
* @param timestampWindow Timestamp validation window in seconds (default: 300)
|
|
27
|
+
* @returns Parsed Slack payload
|
|
28
|
+
* @throws Error if signature is invalid or payload format is unrecognized
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseSlackRequest(request: Request, signingSecret: string, timestampWindow?: number): Promise<SlackHttpPayload>;
|
|
31
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/utils/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAEvD,MAAM,MAAM,gBAAgB,GACzB;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,CAAA;AAE1D;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,MAAM,EACrB,eAAe,GAAE,MAAY,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAyD3B"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Parses and validates a Slack HTTP request.
|
|
4
|
+
*
|
|
5
|
+
* @param request The incoming request
|
|
6
|
+
* @param signingSecret The Slack app signing secret
|
|
7
|
+
* @param timestampWindow Timestamp validation window in seconds (default: 300)
|
|
8
|
+
* @returns Parsed Slack payload
|
|
9
|
+
* @throws Error if signature is invalid or payload format is unrecognized
|
|
10
|
+
*/
|
|
11
|
+
export async function parseSlackRequest(request, signingSecret, timestampWindow = 300) {
|
|
12
|
+
const timestamp = request.headers.get('x-slack-request-timestamp');
|
|
13
|
+
const signature = request.headers.get('x-slack-signature');
|
|
14
|
+
if (!timestamp || !signature) {
|
|
15
|
+
throw new Error('Missing timestamp or signature headers');
|
|
16
|
+
}
|
|
17
|
+
const ts = Number(timestamp);
|
|
18
|
+
const now = Math.floor(Date.now() / 1000);
|
|
19
|
+
if (isNaN(ts) || Math.abs(now - ts) > timestampWindow) {
|
|
20
|
+
throw new Error('Timestamp outside acceptable window');
|
|
21
|
+
}
|
|
22
|
+
const bodyText = await request.text();
|
|
23
|
+
const baseString = `v0:${timestamp}:${bodyText}`;
|
|
24
|
+
const expectedSignature = 'v0=' + createHmac('sha256', signingSecret).update(baseString).digest('hex');
|
|
25
|
+
const signatureBuffer = Buffer.from(signature);
|
|
26
|
+
const expectedBuffer = Buffer.from(expectedSignature);
|
|
27
|
+
if (signatureBuffer.length !== expectedBuffer.length ||
|
|
28
|
+
!timingSafeEqual(signatureBuffer, expectedBuffer)) {
|
|
29
|
+
throw new Error('Invalid signature');
|
|
30
|
+
}
|
|
31
|
+
const contentType = request.headers.get('content-type') || '';
|
|
32
|
+
let body;
|
|
33
|
+
if (contentType.includes('application/json')) {
|
|
34
|
+
try {
|
|
35
|
+
body = JSON.parse(bodyText);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error('Invalid JSON payload');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
42
|
+
const params = new URLSearchParams(bodyText);
|
|
43
|
+
const formBody = parseFormBody(params);
|
|
44
|
+
if (params.has('payload')) {
|
|
45
|
+
try {
|
|
46
|
+
body = JSON.parse(params.get('payload'));
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error('Invalid JSON in form payload');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
body = formBody;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw new Error(`Unsupported content type: ${contentType}`);
|
|
58
|
+
}
|
|
59
|
+
return detectSlackPayload(body);
|
|
60
|
+
}
|
|
61
|
+
function detectSlackPayload(body) {
|
|
62
|
+
if (typeof body !== 'object' || body === null) {
|
|
63
|
+
throw new Error('Invalid payload: not an object');
|
|
64
|
+
}
|
|
65
|
+
const obj = body;
|
|
66
|
+
if (obj.type === 'url_verification' && typeof obj.challenge === 'string') {
|
|
67
|
+
return {
|
|
68
|
+
type: 'url_verification',
|
|
69
|
+
challenge: obj.challenge,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (obj.type === 'event_callback' && obj.event) {
|
|
73
|
+
return {
|
|
74
|
+
type: 'event',
|
|
75
|
+
payload: body,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (obj.type === 'block_actions' && Array.isArray(obj.actions)) {
|
|
79
|
+
return {
|
|
80
|
+
type: 'block_actions',
|
|
81
|
+
payload: body,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (obj.type === 'view_submission' && obj.view) {
|
|
85
|
+
return {
|
|
86
|
+
type: 'view_submission',
|
|
87
|
+
payload: body,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (typeof obj.command === 'string' && typeof obj.text === 'string' && obj.trigger_id) {
|
|
91
|
+
return {
|
|
92
|
+
type: 'slash_command',
|
|
93
|
+
payload: body,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
throw new Error('Unrecognized payload type');
|
|
97
|
+
}
|
|
98
|
+
/** Parses URLSearchParams from form data into a plain object. */
|
|
99
|
+
function parseFormBody(params) {
|
|
100
|
+
const result = {};
|
|
101
|
+
for (const [key, value] of params.entries()) {
|
|
102
|
+
result[key] = value;
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|