Skip to content

Modernize: express-slack/request -> Bolt, Block Kit buttons, add Docker + deploy - #13

Open
ddbruce wants to merge 1 commit into
masterfrom
modernization
Open

Modernize: express-slack/request -> Bolt, Block Kit buttons, add Docker + deploy#13
ddbruce wants to merge 1 commit into
masterfrom
modernization

Conversation

@ddbruce

@ddbruce ddbruce commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Modernizes air off its abandoned express-slack wrapper and the deprecated request library onto @slack/bolt, converts the Yes/No response buttons to Block Kit (your choice), and adds the container + deploy tooling it never had. Same behavior: a dispatch POST posts to the responding + dispatch channels; during the day-crew window (05:55–18:05) the responding message carries Yes/No buttons; clicking within 12 minutes looks up the user and posts "name is RESPONDING / NOT RESPONDING"; late clicks get the same ephemeral notice.

Verified: 9 unit tests pass; boots on Bolt 4 (/health → 200; dispatch webhook rejects bad verification with 401 and echoes dispatch on good; unsigned /slack/events → 401); amd64 image builds with 0 npm vulnerabilities.

⚠️ Like slack-poll, the live Slack button flow can't be exercised here — please smoke-test in staging.

Changes

  • SDK: express-slack (abandoned) + request (deprecated) → @slack/bolt 4.x. Bolt's ExpressReceiver verifies button interactions via the signing secret (the old code did not verify them) and also hosts the external dispatch webhooks (/tmd_slack_notification, /tmd_slack_notification_long) as plain Express routes plus /health. Dropped axios (its only use was the dead OOS check); added dotenv.
  • Buttons → Block Kit: the interactive message's Yes/No buttons are now Block Kit (action_id responding_yes/responding_no, values yes/no) placed inside a colored attachment so the orange color bar is preserved. The other non-interactive messages keep their colored attachments unchanged.
  • Config externalized to env vars (.env + dotenv): SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET (new), AIR_CHANNEL, DISPATCH_CHANNEL, VERIFICATION_TOKEN, PORT (default 5939). Removes the hardcoded channel IDs + port, the var.js/data.json config & OAuth store, and the express-slack OAuth scopes.
  • Packaging: added a Dockerfile (there was none) — node:24-alpine, npm ci, non-root, HEALTHCHECK; ESLint 9 flat config; Jest tests; GitHub Actions CI; scripts/deploy.sh + config.example.sh.

⚠️ Required Slack app config changes

  1. Interactivity Request URLhttps://<host>/slack/events (was /slack_response).
  2. Add a Signing Secret (SLACK_SIGNING_SECRET) — from the Slack app's Basic Information. The old code didn't verify button requests at all; Bolt requires it.
  3. The external dispatch system keeps POSTing to /tmd_slack_notification / _long with the verification field (now VERIFICATION_TOKEN) — unchanged.

🐞 Pre-existing bug — your call (NOT silently changed)

The original out-of-service override (areOos()) called axios.get(...) without await and compared a Promise to 1, so it always evaluated false and never affected which message was posted. I preserved that behavior (the override stays disabled) rather than change it silently. OOS_URL/OOS_TOKEN remain in config. Want me to actually enable the OOS check (so off-hours OOS calls show the responding message)? Say the word and I'll wire it up with proper await + error handling.

Verify in staging

  1. Fire a test dispatch to /tmd_slack_notification during the day → Yes/No buttons appear; click each → status message posts with the right name.
  2. Fire one outside 05:55–18:05 → "Night crew call. No response is needed." (no buttons).
  3. /tmd_slack_notification_long → longtone notices in both channels.
  4. Click a button >12 min after dispatch → the "too long after" ephemeral.

Recommended follow-ups (NOT done)

  • Decide on the OOS override (above).
  • Reconcile license metadata (LICENSE = MIT vs package.json = ISC).

…er + deploy

Behavior-preserving modernization. Same behavior: an external dispatch POST to
/tmd_slack_notification (or _long) posts the dispatch to the responding and
dispatch channels; during the day-crew window (05:55–18:05) the responding
message carries Yes/No buttons; clicking a button (within 12 minutes) looks up
the user and posts "<name> is RESPONDING / NOT RESPONDING"; late clicks get the
same ephemeral notice.

SDK / framework:
- Replace the ABANDONED express-slack wrapper and the DEPRECATED request lib
  with @slack/bolt 4.x. Bolt's ExpressReceiver verifies the button interactions
  via signing secret (the old setup did not verify them at all) and also hosts
  the external dispatch webhooks as plain Express routes + a /health route.
- Drop axios: its only use was the out-of-service check, which never worked
  (see below). dotenv added for config.

Buttons -> Block Kit:
- The interactive "Are you responding?" message's Yes/No buttons are now Block
  Kit (action_id responding_yes/responding_no, values yes/no) inside a colored
  attachment, so the orange color bar is preserved. The other, non-interactive
  messages keep their colored attachments unchanged.

Config / packaging:
- Externalize config to env vars (.env + dotenv): SLACK_BOT_TOKEN,
  SLACK_SIGNING_SECRET (new), AIR_CHANNEL, DISPATCH_CHANNEL, VERIFICATION_TOKEN,
  PORT (default 5939). Removes the hardcoded channel IDs and port, the
  var.js/data.json config + OAuth store, and the express-slack OAuth scopes.
- Add a Dockerfile (there was none): node:24-alpine, npm ci, non-root,
  HEALTHCHECK. Add ESLint 9 flat config, Jest tests (9), GitHub Actions CI, and
  scripts/deploy.sh + config.example.sh (amd64 -> GHCR -> Coolify redeploy).

Pre-existing bug (NOT silently changed): the original areOos() out-of-service
override called axios.get without await and compared a Promise to 1, so it
always evaluated false and never affected message selection. Behavior is
preserved (the override stays disabled); OOS_URL/OOS_TOKEN are kept in config
for when the feature is fixed. See the PR to decide whether to enable it.

Verified: 9 unit tests pass; boots on Bolt 4 (/health 200; dispatch webhook
rejects bad verification with 401 and echoes on good; unsigned /slack/events
401); amd64 image builds with 0 npm vulnerabilities.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant