@crvouga/mockingbird-service-slack 1.5.1 → 1.6.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/CHANGELOG.md CHANGED
@@ -1,14 +1,28 @@
1
1
  # Changelog — @crvouga/mockingbird-service-slack
2
2
 
3
- ## 1.5.1 (2026-09-30)
3
+ ## 1.6.1 (2026-09-30)
4
4
 
5
5
  ### Fixes and improvements
6
6
 
7
- - keep hosted pages inside narrow hosts and popups ([6512323](https://github.com/crvouga/mockingbird/commit/6512323198cb00b99005f3d2cfa5b9f1e6e321a7))
7
+ - keep admin buttons pressed until the action finishes ([0aabfcb](https://github.com/crvouga/mockingbird/commit/0aabfcb5f392aa51e193f9c3f817d5877d92bc6a))
8
+ - size admin select fields like the text inputs beside them ([0fb3ef2](https://github.com/crvouga/mockingbird/commit/0fb3ef2ddbf43376c93a2ae6d87c6c9bf890dafc))
9
+ - keep admin section tabs wired when boot is renamed ([4eda25e](https://github.com/crvouga/mockingbird/commit/4eda25eacd59da048d196f7cc8369d7a02b47bfc))
10
+
11
+ ## 1.6.0 (2026-09-29)
12
+
13
+ ### Features
14
+
15
+ - boot Bolt over Socket Mode ([01322df](https://github.com/crvouga/mockingbird/commit/01322df315c66b1f2f5b53094cbbeeeef6d3ae6c))
8
16
 
9
- ### Dependencies
17
+ ### Fixes and improvements
10
18
 
11
- - `@crvouga/mockingbird-service-sqlite`
19
+ - keep merged kill-bill and slack checks green ([5de12bf](https://github.com/crvouga/mockingbird/commit/5de12bf20b6b209769c632ccd4b9ec24b680a07a))
20
+
21
+ ## 1.5.1 (2026-09-29)
22
+
23
+ ### Fixes and improvements
24
+
25
+ - keep hosted pages inside narrow hosts and popups ([6512323](https://github.com/crvouga/mockingbird/commit/6512323198cb00b99005f3d2cfa5b9f1e6e321a7))
12
26
 
13
27
  ## 1.5.0 (2026-09-29)
14
28
 
package/README.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  > Familiar calls. Faithful echoes. Part of [Mockingbird](https://github.com/crvouga/mockingbird).
4
4
 
5
- Stateful mock of **Slack** for test suites: incoming webhooks and the Web API methods our apps
6
- call, with an **outbox** of everything the app "sent". A suite asserts that an alert fired
5
+ Stateful mock of **Slack** for test suites: incoming webhooks, the Web API methods our apps
6
+ call, and the Socket Mode control plane Bolt needs in order to boot, with an **outbox** of
7
+ everything the app "sent". A suite asserts that an alert fired
7
8
  (`GET /__admin/outbox?webhook=…` or `?channel=…`) without a real workspace, and drives the
8
9
  retry paths (429 with `retry-after`, 5xx) and the terminal ones (`no_text`, `no_service`,
9
10
  `channel_not_found`) with named presets.
@@ -30,6 +31,9 @@ Point every `SLACK_*_WEBHOOK_URL` (`SLACK_PIPELINE_ALERT_WEBHOOK_URL`,
30
31
  `http://127.0.0.1:8808/services/T000/B000/<anything>`. Any path is accepted until you register
31
32
  hooks. The Web API callers hardcode `https://slack.com/api/…`; point them at
32
33
  `http://127.0.0.1:8808/api/…` (for `@slack/web-api`, `slackApiUrl: "http://127.0.0.1:8808/api/"`).
34
+ Bolt in Socket Mode uses that same `clientOptions.slackApiUrl` (Bolt copies it into
35
+ `SocketModeReceiver`) plus an `xapp-` app token. `apps.connections.open` returns a `ws://` URL
36
+ on this process.
33
37
 
34
38
  ```bash
35
39
  npx mockingbird-slack serve --port 8808
@@ -61,7 +65,7 @@ const outbox = await slack.fetch(
61
65
  | Route | Behaviour |
62
66
  | --- | --- |
63
67
  | `POST /services/{T}/{B}/{X}` | Incoming webhook. JSON `{text, blocks?, attachments?, thread_ts?}` (or form `payload=<json>`) → `200 ok` (text/plain). `400 invalid_payload` (not a JSON object), `400 no_text` (no text, blocks or attachments), `400 invalid_blocks`, `400 too_many_attachments`, `404 no_service` (unregistered hook, once any hook is registered), `404 channel_not_found`, `410 channel_is_archived`. |
64
- | `POST /api/chat.postMessage` | `{channel, text?, blocks?, attachments?, thread_ts?}` → `{ok, channel, ts, message}`. `channel` is an id or `#name`; unknown channels are created on first use unless `strictChannels`. Errors: `channel_not_found`, `no_text`, `invalid_blocks`, `invalid_blocks_format`, `msg_too_long`, `is_archived`. |
68
+ | `POST /api/chat.postMessage` | `{channel, text?, blocks?, attachments?, thread_ts?, unfurl_links?}` → `{ok, channel, ts, message}`. `channel` is an id or `#name`; unknown channels are created on first use unless `strictChannels`. The outbox keeps `text`, `blocks`, `unfurl_links`, `thread_ts`, and `workspace` (the team id). Errors: `channel_not_found`, `no_text`, `invalid_blocks`, `invalid_blocks_format`, `msg_too_long`, `is_archived`. |
65
69
  | `POST /api/chat.update` | `{channel, ts, text?, blocks?}` → `{ok, channel, ts, text, message}` with `edited`; `message_not_found`, `cant_update_message` (webhook posts). Omitted blocks are kept. |
66
70
  | `POST /api/chat.postEphemeral` | `{channel, user, text}` → `{ok, message_ts}`; `user_not_found`. Recorded in the outbox with `ephemeral: true`. |
67
71
  | `GET\|POST /api/chat.getPermalink` | `channel`, `message_ts` → `{ok, channel, permalink}` (`https://<domain>.slack.com/archives/C…/p…`); `message_not_found`. |
@@ -71,6 +75,8 @@ const outbox = await slack.fetch(
71
75
  | `GET\|POST /api/users.info`, `/users.lookupByEmail` | `{ok, user}` with `profile.email`/`real_name`; `user_not_found`, `users_not_found`. Seeded: `U0ADA` (`ada@example.com`) and the bot. |
72
76
  | `GET\|POST /api/files.info` | `{ok, file}` with `url_private_download` on `files.slack.com`; `file_not_found`. Seeded: `F0REPORT`. |
73
77
  | `POST /api/views.open` | `{trigger_id, view}` → `{ok, view: {id: "V…", hash, state, …}}`; `invalid_arguments`. |
78
+ | `POST /api/apps.connections.open` | `Authorization: Bearer xapp-…` (header only) → `{ok, url}` where `url` is `ws://<this host>/link/?ticket=…&app_id=…`. One WebSocket per ticket; a second upgrade or a closed ticket is rejected. Missing token → `not_authed`; a bot token → `not_allowed_token_type`; malformed → `invalid_auth`; a token in `revokedAppTokens` → `token_revoked`; an app token that is not in this namespace's `appTokens` (when that list is non-empty) → `invalid_auth`. |
79
+ | WebSocket `/link/?ticket=` | On upgrade, one JSON `hello` (`type`, `connection_info.app_id`, `num_connections`, `debug_info.approximate_connection_time`). No events are pushed. Client pings are ponged; the server pings every 2s. `{envelope_id}` acks are accepted and not answered. `disconnect` reasons `warning`, `refresh_requested`, and `link_disabled` match Slack's envelope (no reconnect URL: `@slack/socket-mode@2.0.4` calls `apps.connections.open` again). |
74
80
 
75
81
  Web API bodies may be JSON or form-encoded (structures JSON-encoded in form fields), as Slack
76
82
  accepts both; read methods also take query arguments. Auth is `Authorization: Bearer xox…` (or a
@@ -82,10 +88,12 @@ a JSON post without `charset` succeeds with Slack's `missing_charset` warning.
82
88
 
83
89
  | Route | Effect |
84
90
  | --- | --- |
85
- | `GET /__admin/outbox?webhook=<path>&channel=<id\|#name>&thread_ts=&source=webhook\|api&since=&limit=` | Every message sent, oldest first: `text`, `blocks`, `attachments`, `thread_ts`, `ts`, `channel`, `webhook`, `method`, `ephemeral`, `edited`, `reactions`. `webhook` takes `/services/T/B/X`, `T/B/X` or a full URL. `GET /__admin/outbox/:id` reads one. |
91
+ | `GET /__admin/outbox?webhook=<path>&channel=<id\|#name>&thread_ts=&source=webhook\|api&since=&limit=` | Every message sent, oldest first: `text`, `blocks`, `attachments`, `thread_ts`, `ts`, `channel`, `webhook`, `method`, `ephemeral`, `edited`, `reactions`, `unfurl_links`, `workspace`. `webhook` takes `/services/T/B/X`, `T/B/X` or a full URL. `GET /__admin/outbox/:id` reads one. |
86
92
  | `POST /__admin/hooks` | `{path, channel?}` or `{hooks: [...]}`: register webhooks (and route them to a channel). Once any hook exists, unknown hooks answer `404 no_service`. `GET` lists, `DELETE [?path=]` removes. |
87
93
  | `POST /__admin/channels`, `/users`, `/files` | Seed workspace records (`{id?, name, is_private?, is_archived?, is_member?}`, `{id, name?, real_name?, email?}`, `{id, name?, mimetype?, size?}`). `GET /__admin/channels`, `/users`, `/views` list them. |
88
- | `PUT /__admin/settings` | `{teamId?, teamName?, teamDomain?, botUserId?, botId?, appId?, tokens?, strictChannels?}` for the calling namespace. |
94
+ | `PUT /__admin/settings` | `{teamId?, teamName?, teamDomain?, botUserId?, botId?, appId?, tokens?, appTokens?, revokedAppTokens?, socketLifetimeMs?, strictChannels?}` for the calling namespace. `socketLifetimeMs` closes an open Socket Mode connection with `reason: "warning"`. |
95
+ | `GET /__admin/socket/connections` | Open and closed Socket Mode sockets for the calling namespace: `namespace`, `appId`, `ticket`, `connectedAt`, `state`, `reconnectCount`. Never tokens. |
96
+ | `POST /__admin/socket/disconnect` | `{reason?: "warning"\|"refresh_requested"\|"link_disabled", code?: 1000}`. Sends Slack's `disconnect` envelope, then closes. `code: 1006` drops the TCP connection instead. |
89
97
 
90
98
  Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n, "params"?: {...}}`):
91
99
 
@@ -97,6 +105,12 @@ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n, "params"?
97
105
  | `channel_not_found` | Webhooks `404 channel_not_found`; Web API channel methods `{ok: false, error: "channel_not_found"}`. |
98
106
  | `no_service` | Webhooks `404 no_service` (a revoked hook). |
99
107
  | `invalid_auth` | Web API `{ok: false, error: "invalid_auth"}` (a revoked token). |
108
+ | `connections_open_invalid_auth` | `apps.connections.open` answers `{ok:false, error:"invalid_auth"}` and mints no ticket. |
109
+ | `connections_open_429` | `apps.connections.open` answers `429 {ok:false, error:"ratelimited"}` with `retry-after` (`params.retryAfter`, default 1). |
110
+ | `connections_open_5xx` | `apps.connections.open` answers `500 internal_error` (`params.status: 503` gives `service_unavailable`). |
111
+ | `socket_close_after_hello` | The next Socket Mode socket sends `hello`, then a normal close. |
112
+ | `socket_abnormal_close` | The next Socket Mode socket sends `hello`, then drops the connection (code 1006). |
113
+ | `socket_hello_latency` | Delay `hello` by `params.latencyMs` milliseconds. The HTTP response itself is not delayed. |
100
114
 
101
115
  ### Namespaces
102
116
 
@@ -106,8 +120,11 @@ maps a bot token or a webhook's `T/B/X` path to a namespace.
106
120
 
107
121
  ### Deliberately not modelled
108
122
 
109
- - Socket Mode, the Events API and interactivity payloads (Bolt in notification-service is not
110
- in the local stack).
123
+ - Inbound Events API and interactivity delivery. Socket Mode's connection lifecycle is
124
+ modelled (`apps.connections.open`, `hello`, ping/pong, `disconnect`) so Bolt can boot and
125
+ shut down; the mock does not push events on connect. A `disconnect` envelope has no
126
+ reconnect URL, matching Slack's documented shape and `@slack/socket-mode@2.0.4`, which
127
+ calls `apps.connections.open` again.
111
128
  - File uploads and downloads: `files.info` answers seeded metadata; the `files.slack.com` URLs
112
129
  are not served.
113
130
  - Real per-method rate-limit tiers: 429s come only from the `rate_limited` preset.