@maroonedsoftware/slack 1.9.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -295,6 +295,35 @@ if (isPolicyResultDenied(result)) throw httpError(401).withInternalDetails(resul
295
295
 
296
296
  The context (`rawBody` + a case-insensitive `getHeader` + `options`) is structurally compatible with `@maroonedsoftware/koa`'s `SignaturePolicyContext<SlackSignatureOptions>`, so the koa `requireSignature` middleware can drive this policy when it's registered under the signature policy name — no koa dependency in this package.
297
297
 
298
+ ## Use with `@maroonedsoftware/comms`
299
+
300
+ The `@maroonedsoftware/slack/comms` subpath adapts this package to the channel-agnostic
301
+ [`@maroonedsoftware/comms`](../comms) router, so a `command` / `action` / `message` handler written
302
+ once runs on Slack and every other wired channel. It declares `@maroonedsoftware/comms` as an
303
+ **optional peer** — the slack core doesn't depend on it.
304
+
305
+ ```ts
306
+ import { SlackClient, SlackConfig, verifySlackSignature } from '@maroonedsoftware/slack';
307
+ import { dispatchSlackCommand, dispatchSlackInteraction, dispatchSlackEvent, createSlackNotifier } from '@maroonedsoftware/slack/comms';
308
+ import { router } from './router.js'; // a shared ChannelRouter
309
+
310
+ router.post('/slack/commands', async (ctx) => {
311
+ const raw = await rawBody(ctx.req, { encoding: 'utf8' });
312
+ verifySlackSignature({ signingSecret: ctx.container.get(SlackConfig).signingSecret, rawBody: raw,
313
+ timestamp: ctx.get('x-slack-request-timestamp'), signature: ctx.get('x-slack-signature') });
314
+ await dispatchSlackCommand(router, ctx.container.get(SlackClient), Object.fromEntries(new URLSearchParams(raw)) as never);
315
+ ctx.status = 200; ctx.body = '';
316
+ });
317
+ ```
318
+
319
+ - `dispatchSlackEvent` (→ `message`/`app_mention`, replies via `chat.postMessage`; also returns the
320
+ `url_verification` challenge), `dispatchSlackCommand` (→ `command`, replies via `response_url`),
321
+ `dispatchSlackInteraction` (→ `action` for `block_actions`). `view_submission`/modals stay on the
322
+ native `SlackInteractionHandlerMap`.
323
+ - `createSlackNotifier(client, router.templates)` sends proactively. Buttons render as a Block Kit
324
+ `actions` block; `reply.sendTemplate(name, data)` renders a registered Slack template;
325
+ `reply.sendNative(payload)` posts raw Block Kit.
326
+
298
327
  ## Limitations
299
328
 
300
329
  - v1 supports a single workspace via the bot token in `SlackConfig`. Multi-workspace OAuth install is out of scope.
@@ -0,0 +1,7 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ export {
5
+ __name
6
+ };
7
+ //# sourceMappingURL=chunk-7QVYU63E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `@maroonedsoftware/slack/comms` — adapter binding the Slack package to the
3
+ * channel-agnostic `@maroonedsoftware/comms` router. Importing this subpath
4
+ * pulls in `@maroonedsoftware/comms` (an optional peer); the slack core does not.
5
+ */
6
+ import { type ChannelRouter, type Notifier, type TemplateRegistry } from '@maroonedsoftware/comms';
7
+ import { SlackClient } from './client/slack.client.js';
8
+ import type { SlackCommandPayload } from './slack.command.handler.js';
9
+ import type { SlackInteractionPayload } from './slack.interaction.handler.js';
10
+ import type { SlackEventsRequest } from './slack.dispatcher.js';
11
+ /**
12
+ * Builds a {@link Notifier} that sends portable messages and registered templates
13
+ * through {@link SlackClient}. The recipient string is either a `response_url`
14
+ * (used as an incoming webhook) or a channel id (`chat.postMessage`).
15
+ */
16
+ export declare const createSlackNotifier: (client: SlackClient, templates: TemplateRegistry) => Notifier;
17
+ /**
18
+ * Dispatches a parsed Slack Events API body. Returns the `url_verification`
19
+ * challenge for the handshake; for `message` / `app_mention` events it routes a
20
+ * normalized `message` event to the {@link ChannelRouter} (replying via
21
+ * `chat.postMessage` to the event's channel). Returns `undefined` otherwise.
22
+ */
23
+ export declare const dispatchSlackEvent: (router: ChannelRouter, client: SlackClient, body: SlackEventsRequest) => Promise<{
24
+ challenge: string;
25
+ } | undefined>;
26
+ /** Dispatches a parsed slash-command payload as a normalized `command` event (reply via `response_url`). */
27
+ export declare const dispatchSlackCommand: (router: ChannelRouter, client: SlackClient, payload: SlackCommandPayload) => Promise<void>;
28
+ /**
29
+ * Dispatches a parsed interactive payload. Only `block_actions` is normalized
30
+ * (to an `action` event keyed by the first action's `action_id`); other types
31
+ * (e.g. `view_submission`) stay on the slack package's native handlers.
32
+ */
33
+ export declare const dispatchSlackInteraction: (router: ChannelRouter, client: SlackClient, payload: SlackInteractionPayload) => Promise<void>;
34
+ //# sourceMappingURL=comms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comms.d.ts","sourceRoot":"","sources":["../src/comms.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAyB,KAAK,aAAa,EAAsB,KAAK,QAAQ,EAAwB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACpK,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAyBhE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,WAAW,EAAE,WAAW,gBAAgB,KAAG,QASrF,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAU,QAAQ,aAAa,EAAE,QAAQ,WAAW,EAAE,MAAM,kBAAkB,KAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAYxJ,CAAC;AAEF,4GAA4G;AAC5G,eAAO,MAAM,oBAAoB,GAAU,QAAQ,aAAa,EAAE,QAAQ,WAAW,EAAE,SAAS,mBAAmB,KAAG,OAAO,CAAC,IAAI,CAYjI,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAU,QAAQ,aAAa,EAAE,QAAQ,WAAW,EAAE,SAAS,uBAAuB,KAAG,OAAO,CAAC,IAAI,CAgBzI,CAAC"}
package/dist/comms.js ADDED
@@ -0,0 +1,129 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-7QVYU63E.js";
4
+
5
+ // src/comms.ts
6
+ import { bindReply, CommsError } from "@maroonedsoftware/comms";
7
+ var render = /* @__PURE__ */ __name((message) => {
8
+ if (!message.buttons?.length) return {
9
+ text: message.text
10
+ };
11
+ return {
12
+ text: message.text,
13
+ blocks: [
14
+ {
15
+ type: "section",
16
+ text: {
17
+ type: "mrkdwn",
18
+ text: message.text
19
+ }
20
+ },
21
+ {
22
+ type: "actions",
23
+ elements: message.buttons.map((b) => ({
24
+ type: "button",
25
+ action_id: b.id,
26
+ text: {
27
+ type: "plain_text",
28
+ text: b.label
29
+ },
30
+ value: b.value ?? b.id
31
+ }))
32
+ }
33
+ ]
34
+ };
35
+ }, "render");
36
+ var deliver = /* @__PURE__ */ __name((client, to, payload) => to.startsWith("http") ? client.postWebhook(payload, to) : client.postMessage({
37
+ channel: to,
38
+ ...payload
39
+ }), "deliver");
40
+ var createSlackNotifier = /* @__PURE__ */ __name((client, templates) => ({
41
+ channel: "slack",
42
+ send: /* @__PURE__ */ __name(async (to, message) => void await deliver(client, to, render(message)), "send"),
43
+ sendTemplate: /* @__PURE__ */ __name(async (to, name, data) => {
44
+ const resolved = templates.render(name, "slack", data);
45
+ if (!resolved) throw new CommsError(`No comms template registered for "${name}"`).withInternalDetails({
46
+ channel: "slack",
47
+ name
48
+ });
49
+ await deliver(client, to, resolved.kind === "native" ? resolved.payload : render(resolved.message));
50
+ }, "sendTemplate"),
51
+ sendNative: /* @__PURE__ */ __name(async (to, payload) => void await deliver(client, to, payload), "sendNative")
52
+ }), "createSlackNotifier");
53
+ var dispatchSlackEvent = /* @__PURE__ */ __name(async (router, client, body) => {
54
+ if (body.type === "url_verification") return {
55
+ challenge: body.challenge
56
+ };
57
+ if (body.type !== "event_callback") return void 0;
58
+ const envelope = body;
59
+ const ev = envelope.event;
60
+ if ((ev.type === "message" || ev.type === "app_mention") && ev.user && !ev.bot_id && ev.subtype !== "bot_message") {
61
+ const channel = ev.channel ?? "";
62
+ const event = {
63
+ channel: "slack",
64
+ kind: "message",
65
+ user: {
66
+ id: ev.user
67
+ },
68
+ conversation: {
69
+ id: channel
70
+ },
71
+ text: ev.text,
72
+ raw: envelope
73
+ };
74
+ await router.dispatch(event, bindReply(createSlackNotifier(client, router.templates), channel));
75
+ }
76
+ return void 0;
77
+ }, "dispatchSlackEvent");
78
+ var dispatchSlackCommand = /* @__PURE__ */ __name(async (router, client, payload) => {
79
+ const event = {
80
+ channel: "slack",
81
+ kind: "command",
82
+ user: {
83
+ id: payload.user_id,
84
+ username: payload.user_name
85
+ },
86
+ conversation: {
87
+ id: payload.channel_id
88
+ },
89
+ text: `${payload.command} ${payload.text}`.trim(),
90
+ command: {
91
+ name: payload.command,
92
+ args: payload.text
93
+ },
94
+ raw: payload
95
+ };
96
+ const to = payload.response_url || payload.channel_id;
97
+ await router.dispatch(event, bindReply(createSlackNotifier(client, router.templates), to));
98
+ }, "dispatchSlackCommand");
99
+ var dispatchSlackInteraction = /* @__PURE__ */ __name(async (router, client, payload) => {
100
+ if (payload.type !== "block_actions") return;
101
+ const first = payload.actions?.[0];
102
+ if (!first) return;
103
+ const channel = payload.channel?.id ?? "";
104
+ const event = {
105
+ channel: "slack",
106
+ kind: "action",
107
+ user: {
108
+ id: payload.user?.id ?? "",
109
+ username: payload.user?.name
110
+ },
111
+ conversation: {
112
+ id: channel
113
+ },
114
+ action: {
115
+ id: first.action_id,
116
+ value: first.value
117
+ },
118
+ raw: payload
119
+ };
120
+ const to = payload.response_url || channel;
121
+ await router.dispatch(event, bindReply(createSlackNotifier(client, router.templates), to));
122
+ }, "dispatchSlackInteraction");
123
+ export {
124
+ createSlackNotifier,
125
+ dispatchSlackCommand,
126
+ dispatchSlackEvent,
127
+ dispatchSlackInteraction
128
+ };
129
+ //# sourceMappingURL=comms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/comms.ts"],"sourcesContent":["/**\n * `@maroonedsoftware/slack/comms` — adapter binding the Slack package to the\n * channel-agnostic `@maroonedsoftware/comms` router. Importing this subpath\n * pulls in `@maroonedsoftware/comms` (an optional peer); the slack core does not.\n */\nimport { bindReply, CommsError, type ChannelRouter, type IncomingEvent, type Notifier, type OutgoingMessage, type TemplateRegistry } from '@maroonedsoftware/comms';\nimport { SlackClient } from './client/slack.client.js';\nimport type { SlackCommandPayload } from './slack.command.handler.js';\nimport type { SlackInteractionPayload } from './slack.interaction.handler.js';\nimport type { SlackEventCallback } from './slack.event.handler.js';\nimport type { SlackEventsRequest } from './slack.dispatcher.js';\n\ntype SlackPayload = Record<string, unknown>;\n\n/** Renders a portable message to a Slack chat payload (`text`, or `text` + Block Kit `actions`). */\nconst render = (message: OutgoingMessage): SlackPayload => {\n if (!message.buttons?.length) return { text: message.text };\n return {\n text: message.text,\n blocks: [\n { type: 'section', text: { type: 'mrkdwn', text: message.text } },\n {\n type: 'actions',\n elements: message.buttons.map(b => ({ type: 'button', action_id: b.id, text: { type: 'plain_text', text: b.label }, value: b.value ?? b.id })),\n },\n ],\n };\n};\n\n/** Delivers a Slack payload: to a `response_url` (http) via webhook, otherwise to a channel id. */\nconst deliver = (client: SlackClient, to: string, payload: SlackPayload): Promise<unknown> =>\n to.startsWith('http')\n ? client.postWebhook(payload as Parameters<SlackClient['postWebhook']>[0], to)\n : client.postMessage({ channel: to, ...payload } as Parameters<SlackClient['postMessage']>[0]);\n\n/**\n * Builds a {@link Notifier} that sends portable messages and registered templates\n * through {@link SlackClient}. The recipient string is either a `response_url`\n * (used as an incoming webhook) or a channel id (`chat.postMessage`).\n */\nexport const createSlackNotifier = (client: SlackClient, templates: TemplateRegistry): Notifier => ({\n channel: 'slack',\n send: async (to, message) => void (await deliver(client, to, render(message))),\n sendTemplate: async (to, name, data) => {\n const resolved = templates.render(name, 'slack', data);\n if (!resolved) throw new CommsError(`No comms template registered for \"${name}\"`).withInternalDetails({ channel: 'slack', name });\n await deliver(client, to, resolved.kind === 'native' ? (resolved.payload as SlackPayload) : render(resolved.message));\n },\n sendNative: async (to, payload) => void (await deliver(client, to, payload as SlackPayload)),\n});\n\n/**\n * Dispatches a parsed Slack Events API body. Returns the `url_verification`\n * challenge for the handshake; for `message` / `app_mention` events it routes a\n * normalized `message` event to the {@link ChannelRouter} (replying via\n * `chat.postMessage` to the event's channel). Returns `undefined` otherwise.\n */\nexport const dispatchSlackEvent = async (router: ChannelRouter, client: SlackClient, body: SlackEventsRequest): Promise<{ challenge: string } | undefined> => {\n if (body.type === 'url_verification') return { challenge: (body as { challenge: string }).challenge };\n if (body.type !== 'event_callback') return undefined;\n\n const envelope = body as SlackEventCallback;\n const ev = envelope.event as { type: string; channel?: string; user?: string; text?: string; bot_id?: string; subtype?: string };\n if ((ev.type === 'message' || ev.type === 'app_mention') && ev.user && !ev.bot_id && ev.subtype !== 'bot_message') {\n const channel = ev.channel ?? '';\n const event: IncomingEvent = { channel: 'slack', kind: 'message', user: { id: ev.user }, conversation: { id: channel }, text: ev.text, raw: envelope };\n await router.dispatch(event, bindReply(createSlackNotifier(client, router.templates), channel));\n }\n return undefined;\n};\n\n/** Dispatches a parsed slash-command payload as a normalized `command` event (reply via `response_url`). */\nexport const dispatchSlackCommand = async (router: ChannelRouter, client: SlackClient, payload: SlackCommandPayload): Promise<void> => {\n const event: IncomingEvent = {\n channel: 'slack',\n kind: 'command',\n user: { id: payload.user_id, username: payload.user_name },\n conversation: { id: payload.channel_id },\n text: `${payload.command} ${payload.text}`.trim(),\n command: { name: payload.command, args: payload.text },\n raw: payload,\n };\n const to = payload.response_url || payload.channel_id;\n await router.dispatch(event, bindReply(createSlackNotifier(client, router.templates), to));\n};\n\n/**\n * Dispatches a parsed interactive payload. Only `block_actions` is normalized\n * (to an `action` event keyed by the first action's `action_id`); other types\n * (e.g. `view_submission`) stay on the slack package's native handlers.\n */\nexport const dispatchSlackInteraction = async (router: ChannelRouter, client: SlackClient, payload: SlackInteractionPayload): Promise<void> => {\n if (payload.type !== 'block_actions') return;\n const first = payload.actions?.[0];\n if (!first) return;\n\n const channel = (payload as { channel?: { id?: string } }).channel?.id ?? '';\n const event: IncomingEvent = {\n channel: 'slack',\n kind: 'action',\n user: { id: payload.user?.id ?? '', username: payload.user?.name },\n conversation: { id: channel },\n action: { id: first.action_id, value: first.value },\n raw: payload,\n };\n const to = payload.response_url || channel;\n await router.dispatch(event, bindReply(createSlackNotifier(client, router.templates), to));\n};\n"],"mappings":"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","names":["bindReply","CommsError","render","message","buttons","length","text","blocks","type","elements","map","b","action_id","id","label","value","deliver","client","to","payload","startsWith","postWebhook","postMessage","channel","createSlackNotifier","templates","send","sendTemplate","name","data","resolved","CommsError","withInternalDetails","kind","sendNative","dispatchSlackEvent","router","body","challenge","undefined","envelope","ev","event","user","bot_id","subtype","conversation","raw","dispatch","bindReply","dispatchSlackCommand","user_id","username","user_name","channel_id","command","trim","args","response_url","dispatchSlackInteraction","first","actions","action"]}