@maroonedsoftware/slack 1.8.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/LICENSE +21 -0
- package/README.md +280 -0
- package/dist/client/slack.client.d.ts +61 -0
- package/dist/client/slack.client.d.ts.map +1 -0
- package/dist/client/slack.logger.adapter.d.ts +21 -0
- package/dist/client/slack.logger.adapter.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +363 -0
- package/dist/index.js.map +1 -0
- package/dist/slack.command.handler.d.ts +50 -0
- package/dist/slack.command.handler.d.ts.map +1 -0
- package/dist/slack.config.d.ts +31 -0
- package/dist/slack.config.d.ts.map +1 -0
- package/dist/slack.dispatcher.d.ts +124 -0
- package/dist/slack.dispatcher.d.ts.map +1 -0
- package/dist/slack.error.d.ts +18 -0
- package/dist/slack.error.d.ts.map +1 -0
- package/dist/slack.event.handler.d.ts +51 -0
- package/dist/slack.event.handler.d.ts.map +1 -0
- package/dist/slack.interaction.handler.d.ts +68 -0
- package/dist/slack.interaction.handler.d.ts.map +1 -0
- package/dist/slack.signature.d.ts +65 -0
- package/dist/slack.signature.d.ts.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ServerkitError } from '@maroonedsoftware/errors';
|
|
2
|
+
/**
|
|
3
|
+
* Domain error raised by the Slack package for non-HTTP failures (e.g.
|
|
4
|
+
* incoming-webhook POST failed, unknown handler dispatch).
|
|
5
|
+
*
|
|
6
|
+
* Extends {@link ServerkitError} so `errorMiddleware` renders a 500 with
|
|
7
|
+
* `{ message, details }` if one of these escapes a route handler. Inside
|
|
8
|
+
* route handlers, throw `httpError(...)` directly for status-coded responses.
|
|
9
|
+
*/
|
|
10
|
+
export declare class SlackError extends ServerkitError {
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Type guard for {@link SlackError}. Narrows `unknown` to `SlackError` so
|
|
14
|
+
* `details`, `internalDetails`, and the chainable setters are accessible
|
|
15
|
+
* without further checks. Returns `true` for any subclass.
|
|
16
|
+
*/
|
|
17
|
+
export declare const IsSlackError: (error: unknown) => error is SlackError;
|
|
18
|
+
//# sourceMappingURL=slack.error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.error.d.ts","sourceRoot":"","sources":["../src/slack.error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;;;;;GAOG;AACH,qBAAa,UAAW,SAAQ,cAAc;CAAG;AAEjD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,UAAyC,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata accompanying every dispatched Slack event. Includes the wrapping
|
|
3
|
+
* envelope fields (team/event IDs) plus the raw `event_callback` payload for
|
|
4
|
+
* handlers that need fields the typed `event` object doesn't expose.
|
|
5
|
+
*/
|
|
6
|
+
export type SlackEventContext = {
|
|
7
|
+
/** Slack workspace / team ID from the envelope. */
|
|
8
|
+
teamId: string;
|
|
9
|
+
/** Unique event ID Slack assigns to each delivery. */
|
|
10
|
+
eventId: string;
|
|
11
|
+
/** Unix timestamp the event was generated. */
|
|
12
|
+
eventTime: number;
|
|
13
|
+
/** Original `event_callback` envelope, untouched. */
|
|
14
|
+
envelope: SlackEventCallback;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Slack `event_callback` envelope. The shape is documented at
|
|
18
|
+
* https://api.slack.com/types/event. We type the wrapper but leave the inner
|
|
19
|
+
* `event` as `Record<string, unknown>` because the union of all Slack event
|
|
20
|
+
* payloads is large and consumers typically narrow per handler.
|
|
21
|
+
*/
|
|
22
|
+
export type SlackEventCallback = {
|
|
23
|
+
type: 'event_callback';
|
|
24
|
+
team_id: string;
|
|
25
|
+
api_app_id: string;
|
|
26
|
+
event: {
|
|
27
|
+
type: string;
|
|
28
|
+
} & Record<string, unknown>;
|
|
29
|
+
event_id: string;
|
|
30
|
+
event_time: number;
|
|
31
|
+
authorizations?: unknown[];
|
|
32
|
+
is_ext_shared_channel?: boolean;
|
|
33
|
+
event_context?: string;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Handler for a single Slack event type (e.g. `app_mention`, `message`,
|
|
38
|
+
* `reaction_added`). Registered in {@link SlackEventHandlerMap}.
|
|
39
|
+
*
|
|
40
|
+
* Handlers should ack quickly — Slack retries any event that doesn't get a
|
|
41
|
+
* 2xx response within ~3 seconds. For slow work, enqueue a job
|
|
42
|
+
* (`@maroonedsoftware/jobbroker`) inside `handle` and return immediately.
|
|
43
|
+
*/
|
|
44
|
+
export interface SlackEventHandler<TEvent extends {
|
|
45
|
+
type: string;
|
|
46
|
+
} & Record<string, unknown> = {
|
|
47
|
+
type: string;
|
|
48
|
+
} & Record<string, unknown>> {
|
|
49
|
+
handle(event: TEvent, context: SlackEventContext): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=slack.event.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.event.handler.d.ts","sourceRoot":"","sources":["../src/slack.event.handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,QAAQ,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB,CAAC,MAAM,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvI,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The supported interactive payload types Slack POSTs to the interactivity
|
|
3
|
+
* endpoint. Each maps to a different identifier shape (see
|
|
4
|
+
* {@link interactionRouteKey}).
|
|
5
|
+
*/
|
|
6
|
+
export type SlackInteractionType = 'block_actions' | 'view_submission' | 'view_closed' | 'shortcut' | 'message_action' | string;
|
|
7
|
+
/**
|
|
8
|
+
* Loose typing for the interactive payload; consumers narrow per handler.
|
|
9
|
+
* Slack's payloads vary by type, but every variant has a `type` field plus
|
|
10
|
+
* one of: `actions[].action_id`, `view.callback_id`, or top-level `callback_id`.
|
|
11
|
+
*/
|
|
12
|
+
export type SlackInteractionPayload = {
|
|
13
|
+
type: SlackInteractionType;
|
|
14
|
+
team?: {
|
|
15
|
+
id: string;
|
|
16
|
+
domain?: string;
|
|
17
|
+
};
|
|
18
|
+
user?: {
|
|
19
|
+
id: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
};
|
|
22
|
+
trigger_id?: string;
|
|
23
|
+
response_url?: string;
|
|
24
|
+
actions?: Array<{
|
|
25
|
+
action_id: string;
|
|
26
|
+
block_id?: string;
|
|
27
|
+
value?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}>;
|
|
30
|
+
view?: {
|
|
31
|
+
id: string;
|
|
32
|
+
callback_id: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
callback_id?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Optional response Slack accepts for `view_submission` / `view_closed`
|
|
40
|
+
* payloads (e.g. to display validation errors or update a modal).
|
|
41
|
+
*/
|
|
42
|
+
export type SlackInteractionResponse = {
|
|
43
|
+
response_action?: 'errors' | 'update' | 'push' | 'clear';
|
|
44
|
+
errors?: Record<string, string>;
|
|
45
|
+
view?: unknown;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Handler for one interactive payload, keyed in {@link SlackInteractionHandlerMap}
|
|
50
|
+
* by `${type}:${identifier}` — see {@link interactionRouteKey}.
|
|
51
|
+
*/
|
|
52
|
+
export interface SlackInteractionHandler {
|
|
53
|
+
handle(payload: SlackInteractionPayload): Promise<SlackInteractionResponse | void>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Computes the routing key used by {@link SlackDispatcher.dispatchInteraction}
|
|
57
|
+
* to look a handler up in {@link SlackInteractionHandlerMap}.
|
|
58
|
+
*
|
|
59
|
+
* - `block_actions` → `block_actions:<first action.action_id>`
|
|
60
|
+
* - `view_submission` / `view_closed` → `<type>:<view.callback_id>`
|
|
61
|
+
* - `shortcut` / `message_action` → `<type>:<callback_id>`
|
|
62
|
+
* - any other type with a `callback_id` → `<type>:<callback_id>`
|
|
63
|
+
*
|
|
64
|
+
* @returns The routing key, or `undefined` if the payload doesn't carry an
|
|
65
|
+
* identifier we can route on (e.g. a `block_actions` payload with no actions).
|
|
66
|
+
*/
|
|
67
|
+
export declare const interactionRouteKey: (payload: SlackInteractionPayload) => string | undefined;
|
|
68
|
+
//# sourceMappingURL=slack.interaction.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.interaction.handler.d.ts","sourceRoot":"","sources":["../src/slack.interaction.handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG,iBAAiB,GAAG,aAAa,GAAG,UAAU,GAAG,gBAAgB,GAAG,MAAM,CAAC;AAEhI;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IAClG,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,eAAe,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAAC;CACpF;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,uBAAuB,KAAG,MAAM,GAAG,SAmB/E,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Default replay-protection window in seconds (5 minutes — matches Slack's recommendation). */
|
|
2
|
+
export declare const SLACK_SIGNATURE_DEFAULT_MAX_AGE_SECONDS = 300;
|
|
3
|
+
/**
|
|
4
|
+
* Reason codes attached to {@link SlackError.internalDetails} when verification
|
|
5
|
+
* fails. Useful for callers that want to log structured reasons without
|
|
6
|
+
* pattern-matching on error messages.
|
|
7
|
+
*/
|
|
8
|
+
export type SlackSignatureFailureReason = 'missing_timestamp' | 'invalid_timestamp' | 'stale_timestamp' | 'missing_signature' | 'invalid_signature';
|
|
9
|
+
/**
|
|
10
|
+
* Inputs to {@link verifySlackSignature}. All values are taken verbatim from
|
|
11
|
+
* the request — the helper does no header lookups or body reads of its own.
|
|
12
|
+
*/
|
|
13
|
+
export type VerifySlackSignatureInput = {
|
|
14
|
+
/** App signing secret (`SlackConfig.signingSecret`). */
|
|
15
|
+
signingSecret: string;
|
|
16
|
+
/** Raw, unparsed request body — exactly as Slack sent it. */
|
|
17
|
+
rawBody: string;
|
|
18
|
+
/** Value of the `X-Slack-Request-Timestamp` header. */
|
|
19
|
+
timestamp: string | undefined;
|
|
20
|
+
/** Value of the `X-Slack-Signature` header (e.g. `"v0=abc123…"`). */
|
|
21
|
+
signature: string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum age in seconds before the request is rejected as a replay.
|
|
24
|
+
* Defaults to {@link SLACK_SIGNATURE_DEFAULT_MAX_AGE_SECONDS}.
|
|
25
|
+
*/
|
|
26
|
+
maxAgeSeconds?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Override for the current Unix time in seconds. Mostly useful for tests;
|
|
29
|
+
* defaults to `Math.floor(Date.now() / 1000)`.
|
|
30
|
+
*/
|
|
31
|
+
now?: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Verifies a Slack request signature against the app signing secret.
|
|
35
|
+
*
|
|
36
|
+
* Implements Slack's v0 scheme:
|
|
37
|
+
* 1. Reject the request if `X-Slack-Request-Timestamp` is missing, non-numeric,
|
|
38
|
+
* or older than `maxAgeSeconds` (replay protection).
|
|
39
|
+
* 2. Compute `v0=` + `HMAC-SHA256(signingSecret, "v0:{timestamp}:{rawBody}")`
|
|
40
|
+
* as hex.
|
|
41
|
+
* 3. Compare against the provided `X-Slack-Signature` value using a
|
|
42
|
+
* constant-time compare.
|
|
43
|
+
*
|
|
44
|
+
* Pure: no request/context coupling. The caller extracts the headers and raw
|
|
45
|
+
* body from whatever transport it's using and passes them in.
|
|
46
|
+
*
|
|
47
|
+
* @throws {@link SlackError} on any failure. The error's `internalDetails.reason`
|
|
48
|
+
* is one of {@link SlackSignatureFailureReason}; map to HTTP 401 at the route boundary.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* try {
|
|
53
|
+
* verifySlackSignature({
|
|
54
|
+
* signingSecret: config.signingSecret,
|
|
55
|
+
* rawBody,
|
|
56
|
+
* timestamp: req.headers['x-slack-request-timestamp'],
|
|
57
|
+
* signature: req.headers['x-slack-signature'],
|
|
58
|
+
* });
|
|
59
|
+
* } catch (err) {
|
|
60
|
+
* throw httpError(401).withCause(err);
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare const verifySlackSignature: (input: VerifySlackSignatureInput) => void;
|
|
65
|
+
//# sourceMappingURL=slack.signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.signature.d.ts","sourceRoot":"","sources":["../src/slack.signature.ts"],"names":[],"mappings":"AAGA,gGAAgG;AAChG,eAAO,MAAM,uCAAuC,MAAM,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,2BAA2B,GACnC,mBAAmB,GACnB,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,GACnB,mBAAmB,CAAC;AAExB;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,wDAAwD;IACxD,aAAa,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,qEAAqE;IACrE,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,yBAAyB,KAAG,IAkDvE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maroonedsoftware/slack",
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"description": "Slack utilities for ServerKit.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Marooned Software",
|
|
7
|
+
"url": "https://github.com/MaroonedSoftware/serverkit"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/MaroonedSoftware/serverkit/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/MaroonedSoftware/serverkit/packages/slack#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"backend",
|
|
15
|
+
"slack",
|
|
16
|
+
"serverkit",
|
|
17
|
+
"typescript"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/MaroonedSoftware/serverkit.git"
|
|
22
|
+
},
|
|
23
|
+
"private": false,
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist/**"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@slack/web-api": "^7.15.1",
|
|
34
|
+
"injectkit": "^1.2.0",
|
|
35
|
+
"@maroonedsoftware/errors": "1.6.0",
|
|
36
|
+
"@maroonedsoftware/logger": "1.1.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@repo/config-eslint": "0.2.1",
|
|
40
|
+
"@repo/config-typescript": "0.1.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration",
|
|
44
|
+
"build:ci": "eslint --max-warnings=0 && pnpm run build",
|
|
45
|
+
"lint": "eslint --fix",
|
|
46
|
+
"format": "prettier --write .",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:ci": "vitest run --coverage"
|
|
49
|
+
}
|
|
50
|
+
}
|