-
Notifications
You must be signed in to change notification settings - Fork 664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Socket Mode Rewrite to Python(ish) Implementation #1781
Conversation
- added some more jsdocs - if a logger is provided, also override the SlackWebSocket logger - fix emitting WS messages to the client - factor out connection termination into a `terminate()` method - do not reconnect on an explicit `disconnect()` call to the client - add and emit an authenticated state to match existing lifecycle events
…slint rules, using import for package json now that we accept importing json files.
…kage json to reflect that.
@@ -7,133 +7,11 @@ anchor_links_header: Usage | |||
|
|||
# Slack Socket Mode | |||
|
|||
For baseline package documentation, please see the project's [`README`](https://github.com/slackapi/node-slack-sdk/tree/main/packages/socket-mode#readme) or the [documentation on npm](https://www.npmjs.com/package/@slack/socket-mode). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mainly axing content in here, as it used to be a straight copy/paste from the socket mode README. No need to duplicate, just link to the duplicated content and keep this area for additional documentation (if necessary).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this sounds good
"main": "dist/index.js", | ||
"types": "./dist/index.d.ts", | ||
"main": "dist/src/index.js", | ||
"types": "./dist/src/index.d.ts", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes are needed to enable the resolveJsonModule
option in the tsconfig.json
, as setting that option changes the output of the TypeScript compiler.
const instanceId = instanceCount; | ||
instanceCount += 1; | ||
export default { | ||
getLogger: function getLogger(name: string, level: LogLevel, existingLogger?: Logger): Logger { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This restructuring of exports-under-a-single-exported-object allows for easier mocking without the need for a tool like rewire
or proxyquire
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome 💯 I'm a fan of the OOP approach, this version is definitely easier to understand
I think respecting object encapsulation instead of making things public with _
prefix will yield more maintainable code
Faking/Mocking a socket mode server to test the the intended behavior of SlackWebSocket
seems like a viable long term testing solution since this API is stable
this.logger.debug('isActive(): websocket not instantiated!'); | ||
return false; | ||
} | ||
this.logger.debug(`isActive(): websocket ready state is ${WS_READY_STATES[this.websocket.readyState]}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious to know why this array
pattern is used instead of some form of Enum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No specific reason! I can change it to an enum if you'd prefer that - lemme know
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the enum
it should make the code self explanatory, the comment on the following will no longer be needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm looking at this, the reason I had the array is that the websocket ready state returns an integer. Unclear how I would map from an integer to the enum? Enums seem to map variable names to integers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh I see, adding the Enum means you need to search for the state based on the integer
In that case sticking to the array
pattern seems valid
…etection, fixed an issue with running unit vs. integration tests locally, added integration tests for ping and pong events and stale connection detection/reconnection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this great work; I don't see any issues so far
@@ -7,133 +7,11 @@ anchor_links_header: Usage | |||
|
|||
# Slack Socket Mode | |||
|
|||
For baseline package documentation, please see the project's [`README`](https://github.com/slackapi/node-slack-sdk/tree/main/packages/socket-mode#readme) or the [documentation on npm](https://www.npmjs.com/package/@slack/socket-mode). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this sounds good
The package exports a `SocketModeClient` class. Your app will create an instance of the class for each workspace it communicates with. Creating an instance requires an [**app-level token**][app-token] from Slack. Apps connect to the **Socket Mode** API using an [**app-level token**][app-token], which starts with `xapp`. | ||
|
||
Note: **Socket Mode** requires the `connections:write` scope. Navigate to your [app configuration](https://api.slack.com/apps) and go to the **OAuth and Permissions** section to add the scope. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch; this had been completely incorrect
…fter the client starts, and not after the timeout period.
Thanks for the reviews! I did see some CI failures with the integration tests, and I realized some of the sequencing of events / websocket behaviour in the test was not ideal and probably brittle on a weaker machines (like GitHub Actions). Running them once on my local machine tended to pass them, but if I ran the tests like this instead:
.. after about 10 or so runs I could trigger a failure. I made a change in the tests to make them more resilient (any event-based waiting promises should be created after Also confirming that the longevity test I had set up has been operating flawlessly for a week. Merging this! Hooray! I will likely publish a 2.0 release of socket-mode soon! |
Socket Mode Rewrite
This PR ports, as much as possible, the Python implementation of the Socket Mode client to node.
I have been running a longevity test of an app, in production, on a busy workspace, using this PR since April 24th without issue (the app reconnects gracefully when Slack sends a
disconnect
message, which is what we expect).Why?
The existing node socket-mode package uses
finity
, an old finite state machine configuration package. While it has served us well, it has been difficult to maintain, with tricky semantics. Additionally, the existing socket-mode implementation is almost a straight copy-paste of thertm-api
module, which is very old.Breaking Changes
authenticated
. I did not find it useful, it just represented that the client retrieved the WS URL to connect to via a successful response from theapps.connections.open
HTTP API.unable_to_start_socket_mode
. I felt like theerror
event was sufficient?public
onSocketModeClient
have been removed:connected
,authenticated
andisActive()
, thoughisActive()
could easily be added back in.What's The Same?
Many of the developer-facing APIs have remained the same:
EventEmitter
-based design, where the client emits the events described in the README, remain identical. This should ease the upgrading path for any projects dependent on this package, including bolt-js.SocketModeClient
's standard public API also remains the same?start()
anddisconnect()
for sure, butisActive()
is now moved toSlackWebSocket
, so that's a major change - though to be fair, we never documented these APIs.Internal Design Changes
SocketModeClient
aims to implement similar interfaces as Python'sSocketModeClient
class, as well as itsBaseSocketModeClient
.Client
is responsible for retrieving the WebSocket URL to connect to (by interacting with theapps.connections.open
HTTP API) and managing reconnections. We can consider these as application-level concerns.SlackWebSocket
aims to implement similar interfaces as Python'sConnection
class. Luckily for the node implementation, many of the low-level details of the WebSocket protocol are encapsulated in thews
dependency.SlackWebSocket
concerns itself with managing and maintaining a single WebSocket connection and nothing more; any WS-specific events relevant to application functioning (e.g. disconnects) are raised back to theClient
for it to deal with.IMO this leads to a nice separation of concerns.
Concerns / Questions / Request for Feedback
_
just to expose them for unit tests?